[Redux] 기초 문법 정리

js43o·2021년 11월 4일
0
post-custom-banner

리덕스는 Flux 패턴을 바탕으로 하며 리액트에서 더욱 체계적인 상태 관리를 위해 만들어진 라이브러리이다.
주요 구성 요소는 다음과 같다.

1. 스토어

현재 state와 reducer를 담고 있는 객체이며 한 애플리케이션에 한 개만 존재할 수 있다. 최상위 컴포넌트를 Provider로 감싸서 store를 넘겨주면 하위 컴포넌트에서 state 값을 참조하거나 액션을 dispatch 할 수 있게 된다.

const store = createStore(reducer);
<Provider store={store}>
  <App />
</Provider>

2. 액션

2-1. 액션 타입

const INCREASE = 'counter/increase';

액션 타입을 정의한다.

2-2. 액션 생성 함수

const increase = () => ({ type: INCREASE });

액션 객체를 생성하는 함수.

2-3. 리듀서

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREASE:
      return { number: state.number + 1 }
  }
};

특정 액션이 발생했을 때 각 액션마다 어떤 일을 수행할지 정의한다.
현재 state와 발생한 action을 인수로 받으며, 새로운 state를 반환한다.
리듀서는 순수 함수여야 한다. 다른 값에 의존하지 않고 state의 불변성을 유지해야 한다.

*redux-actions

액션 생성 함수와 리듀서를 더욱 간편하게 작성할 수 있게 해주는 라이브러리이다.

const insert = createAction(INSERT, text => ({ id: id++, text }));
const reducer = handleActions({
    [INSERT]: (state, action) => ({
      ...state,
      todos: state.todos.concat(action.payload)
    })
  },
  initialState
);

createAction 함수는 액션 타입과 '액션 생성 함수에 전달한 인수를 맵핑해주는 함수'를 인수로 받는다. 반환 값은 액션 생성 함수이다.
handleActions는 액션 타입을 중심으로 리듀서를 정의하는 함수이다. 액션 생성 함수에 전달된 인수는 각 업데이트 함수에서 action.payload로 조회할 수 있다.

const rootReducer = combineReducers({ counter, todos, ... });
export default rootReducer;

한 앱 안에 여러 리듀서가 있을 경우, 하나의 루트 리듀서로 합쳐주는 작업이 필요하다.

3. 컴포넌트

리덕스를 이용한 앱을 만들 때 컴포넌트를 두 가지 종류로 나눌 수 있다.

3-1. 컨테이너 컴포넌트

리덕스 스토어와 직접적으로 연결되어 있는 컴포넌트이다. 스토어로부터 state를 참조하거나 dispatch 작업을 하는 함수를 정의하여 프레젠테이셔널 컴포넌트에게 넘겨줄 수 있다.

// connect 이용
const CounterContainer = (number, increase) => {
  <Counter number={number} onIncrease={increase} />
};
export default connect(
  state => ({ number: state.number }),
  dispatch => ({ increase: () => dispatch(increase()) })
)(CounterCountainer)

처음엔 react-redux의 connect 문법을 활용하여 스토어와 연결하는 방법을 배웠다.
connect는 'store와 dispatch를 각자 어떻게 컨테이너의 props으로 넘겨줄 지(mapping)를 정의하는 함수'를 인수로 받으며 반환 값은 함수이다.
이 반환된 함수에 다시 컨테이너 컴포넌트를 인수로 넣어주면 스토어와 연결된다.

export default connect(
  state => ({ number: state.number }),
  {
    increase,
  }
)(CounterCountainer)

connect의 두 번째 인수로 이렇게 함수 대신 액션 생성 함수로 이루어진 객체를 넘겨줘도 된다. (대신, 액션 생성 함수의 이름과 컨테이너 컴포넌트에서 인수로 받는 이름이 같아야 함)
액션 생성 함수를 받아서 디스패치까지 해주는 함수로 바꿔준다고도 볼 수 있겠다.

connect 문법 대신 컨테이너 컴포넌트 내에서 hooks를 사용할 수도 있다.

// hooks 이용
const CounterContainer = () => {
  const number = useSelector(state => state.number);
  const dispatch = useDispatch();
  ...
};

개인적으로는 이 방법이 훨씬 편하게 느껴졌다.

3-2. 프레젠테이셔널 컴포넌트

컨테이너 컴포넌트로부터 받은 props를 이용하여 단순히 렌더링 해주는 컴포넌트이다.

const Counter = ({ number, onIncrease }) => {
  return (
    <div>{number}</div>
    <button onClick={() => onIncrease()} />
  );
};


처음 리덕스를 접했을 때는 뭔가 많아서 좀 복잡하게 느껴졌는데, 정리를 해놓고 보니 그렇게 어렵지 않았다.
요즘은 hooks 덕분인지 컨테이너 컴포넌트와 프레젠테이셔널 컴포넌트를 굳이 구분하지 않는 경우가 많다고 한다. 또, 웬만하면 리덕스를 쓸 때 Redux Toolkit을 함께 사용하는 것 같다. 조금 더 공부를 해봐야 겠다.

profile
공부용 블로그
post-custom-banner

0개의 댓글