티스토리 뷰

Front-end/JavaScript

Redux에 대하여

nauni 2020. 12. 23. 11:47

Redux에 대하여

Redux란 ?

리덕스란 상태관리 라이브러리이다. 글로벌 상태관리를 할 때 효과적이며, 애플리케이션에서 상태를 보다 효율적으로 관리할 수 있게 도와준다. 리덕스 라이브러리를 설치하여 JS에서 사용할 수도 있고, 리액트 등의 도구와 함께 사용할 수도 있다.

ReduxConcept

앱이 지니고 있는 상태로직이 들어있는 스토어를 통해서 원하는 상태와 함수를 직접 주입하여 관리한다. 컴포넌트 끼리 상태와 로직을 주고 받는 것이 아니라 좀 더 전역적으로 분리되어 관리하는 개념이다. 다양한 로직에 따라 상태변화를 반영해야 하는 상황에서 상태를 한 스토어에서 관리하고 반영할 수 있는 장점을 가지고 있다. 상태에 변화를 일으키기 위해서는 액션(Action)을 스토어에 전달한다.

Reduxmap

스토어는 초기에 생성해준다. 리듀서, 액션, 구독할 리스너, 렌더는 개인이 함수를 만들어 지정한다. 스토어 내장함수 subscribe를 사용하여 상태 변화마다 실행 될 함수(리스너)를 구독해준다. dispatch를 사용하여 액션을 전달한다. 리듀서가 이전 상태 값을 받아 액션에 따라 변화된 상태를 리턴하면 구독된 함수(리스너)가 상태가 변할 때마다 실행된다.

  1. 스토어 : 애플리케이션의 상태 값들을 내장한다. 단일 스토어를 사용한다. 스토어 내부에는 상태, 리듀서, 몇가지 내장함수(dispatch, subscribe, getState) 가 포함되어 있다.
//스토어 생성예시
const store = createStore(reducer);
  1. 리듀서 : 상태를 변화시키는 로직이 있는 함수이다. 리듀서는 stateaction 두가지 파라미터를 받는다.
//리듀서는 만들어 줘야 한다.
function reducer(state, action) {
  // 상태 업데이트 로직, if문이나 switch문을 사용하여 액션에 따라 어떤 상태를 리턴할지 로직을 작성할 수 있다.
  return newState;
}
  1. 액션 : 상태 변화를 일으킬 때 참조하는 객체이다. 즉, 변화가 필요할 때 액션으로 변화를 발생시킨다. 액션 객체는 type 필드를 필수적으로 가지고 있어야한다. 그 외의 값은 옵션이다.
const changeInput = (text) => ({
  type: "CHANGE_INPUT",
  text,
});
  1. 디스패치 : 액션을 스토어에 전달하는 것을 의미한다. 디스패치는 액션을 파라미터로 전달한다.
store.dispatch(action);
  1. 구독 : 스토어 값이 필요한 컴포넌트는 스토어를 구독한다. 함수형태를 파라미터로 받는다. subscribe에 특정 함수를 전달하면, 디스패치 될때마다 해당 함수가 호출된다.
// listener에 함수를 등록해준다. render를 리스너로 등록
store.subscribe(render);
  1. 렌더 : 브러우저에 나타낸다.
//렌더함수에서 getState로 스토어에서 현재 상태를 가져온다.
const render = () => {
  const state = store.getState();
  //이후 렌더 함수 로직
};

리덕스 실행 예시 블로그 - 카운터

리덕스 사용시 주의사항

1. 단일 스토어를 사용한다.

2. 상태는 읽기 전용이다.

리덕스에서 상태를 업데이트할 때는 컴포넌트의 state를 다룰 때처럼 값을 직접 수정하면 안된다. 새로운 객체를 만든 후 그 안에 상태를 정의해야한다. 리덕스 역시 shallow comparision을 사용하므로 객체 자체를 업데이트 해주어야 한다. 배열을 사용할 경우 concat, map, filter 등 연산처리된 새로운 배열을 리턴하는 함수를 사용한다. Object.assign이나 spread 연산자를 많이 사용하여 업데이트 하기도 한다.

3. 리듀서는 순수 함수여야 한다.

순수함수라는 것은 동일한 input에서 동일한 output인 함수를 뜻한다. 리듀서에서는 이전의 상태를 받아 액션으로 새로운 상태를 반횐한다. 이전의 상태는 건드리지 않고, 변화를 일으킨 새로운 상태 객체를 반환한다.

Vanilla JS로 리덕스를 만들며 이해하기

reducer.js
리듀서를 함수를 만든다. 리듀서에서는 각 action의 타입별로 실행할 로직을 설정해준다.

export function reducer(state, { type, payload }) {
  switch (type) {
    case "ADD": {
      state = {
        ...state,
        [payload.id]: {
          id: payload.id,
          content: payload.text,
          isCompleted: false,
        },
      };
      return state;
    }
    case "DELETE": {
      const updated = { ...state };
      delete updated[payload.id];
      state = updated;
      return state;
    }
    case "TOGGLE": {
      const updated = { ...state };
      updated[payload.id] = {
        ...updated[payload.id],
        isCompleted: !updated[payload.id].isCompleted,
      };
      state = updated;
      console.log(state);
      return state;
    }
  }
}

export const actionCreator = (type, payload = {}) => ({
  type,
  payload: { ...payload },
});

createStore.js
스토어 함수에서는 state를 지정하고, dispatch, subscribe, getState 를 구현한다. 매번 상태 변화 때마다 실행되여야 하는 함수는 구독을 해준다. 액션 값을 dispatch의 인자로 넣는다. 리듀서를 통해 액션이 실행되어 리턴된 state를 스토어 안의 state에 업데이트 하고, dispatch는 구독된 함수(렌더)를 실행하도록 만든다.

export default function createStore(reducer) {
  let state;
  const listeners = [];
  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(({ subscriber }) => {
      subscriber();
    });
  };

  const subscribe = (subscriber) => {
    listeners.push({ subscriber });
  };

  const getState = () => {
    return state;
  };
  return { dispatch, subscribe, getState };
}

App.js
const store = createStore(reducer);를 통해 store를 초기화한다. 상태 변화가 일어날 때마다 렌더를 사용할 것이므로 렌더함수를 구독한다. Add, delete, toggle마다 dispatch에 액션을 등록한다. dispatch는 리듀서에 의해 액션마다 state값을 리턴한다. 구독되어있는 render함수에 의해 변화된 state가 렌더된다.

import createStore from "./createStore.js";
import { reducer, actionCreator } from "./reducer.js";

const store = createStore(reducer);

store.subscribe(render);

const makeId = () => Date.now();
const onAdd = (text, id) => actionCreator("ADD", { text, id });
const onDelete = (id) => actionCreator("DELETE", { id });
const onToggle = (id) => actionCreator("TOGGLE", { id });
const $ul = document.querySelector(".todo-list");
const $input = document.querySelector(".new-todo");

$input.addEventListener("keypress", (e) => {
  if ($input.value && e.key === "Enter") {
    store.dispatch(onAdd($input.value, makeId()));
    $input.value = "";
  }
});
$ul.addEventListener("click", (e) => {
  const eId = parseInt(e.target.closest("li").id);
  if (e.target.className === "destroy") {
    store.dispatch(onDelete(eId));
  }
  if (e.target.className === "toggle") {
    store.dispatch(onToggle(eId));
  }
});

function render() {
  const state = store.getState();
  if (!state) return;
  const todos = Object.keys(state)
    .map((key) => {
      return `
      <li id=${state[key].id} class=${
        state[key].isCompleted ? "completed" : ""
      }>
        <div class="view">
          <input class="toggle" type="checkbox" ${
            state[key].isCompleted ? "checked" : ""
          } />
          <label class="label">${state[key].content}</label>
          <button class="destroy"></button>
        </div>
        <input class="edit" value="${state[key].content}" />
      </li>`;
    })
    .join("");
  return ($ul.innerHTML = todos);
}

References

생활코딩_리덕스
블랙커피 스터디_참고 코드
리덕스 개념 블로그
리덕스 개념 블로그2

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함