Redux - Redux Toolkit

ryan·2022년 6월 23일
0

Redux toolkit

redux의 한계

  1. 액션을 발생시킬 때 식별자에 오타가 발생할 수 있고, 식별자 이름이 중복되어 충돌을 발생시킬 수 있음.
  2. 관리하는 데이터가 많아질수록 상태도 많아짐. 리듀서 함수의 길이도 길어지고 유지보수에 어려움이 발생함.
  3. 상태의 불변성. 항상 새로운 state snapshot을 반환시켜야 하고, 특히 중첩된 배열과. 객체 등 복잡한 데이터를 변경할 때 실수가 발생할 수 있음.

위의 단점을 redux toolkit이 보완해준다.

redux toolkit

: redux toolkit은 더 간결하고 효율적으로 redux를 적용하기 위해 사용하는 redux 도구의 모음이고 아래와 같은 이점이 있다.

  1. 기존 redux에서 만들어야 했던 boiler plate를 제거.
  2. redux-devtools,immerjs,redux-thunk,reselect등의 라이브러리가 내장
  3. action 식별자가 string이 아닌, 함수형으로 작성되어 오타를 방지할 수 있다.

Redux toolkit 사용

: 이전 포스팅에서 작성했던 redux를 redux toolkit에 맞게 리팩토링하였다.

1. Reducer 대신 createSlice를 사용

  • createSlice는 기존에 action creater, reducer 등 별도로 만들었던 여러 redux 구현체를 하나의 객체로 모으며 예시는 아래와 같다.
  • 식별자, state, reducer를 하나의 객체로 만들고, switch문 대신 각 액션마다 함수로 구현한다.
  • 이 때, 내장되어있는 immerjs로 인해 불변성이라는 규칙을 벗어나 기존 state를 직접적으로 수정할 수 있다.
  • 컴포넌트에서 dispatch하기 위해 생성한 slice를 (생성한slice변수명).actions 객체를 export한다.
import {createSlice} from '@reduxjs/toolkit';
const initialState = {counter: 0};

// createSlice
// 액션을 전달하는 역할.
const counterSlice = createSlice({
  name: 'counter',
  // name은 각 slice의 식별자이며 겹치지 않게 작성,
  initialState,
  reducers: {
    // 메서드들은 redux에 의해 호출되고 현재 상태를 받음.
    // 서로 다른 리듀서를 구별해놓고 각각 맞는 액션을 발생시킴.
    increment(state) {
      state.counter++;
    },
    // 기존 상태를 변경하는 것 같지만 redux/tookit은 immer라는 패키지를 사용해서 기존 state를 변경시키는 코드를 감지하고 자동으로 오버라이딩하도록 도와줌.
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      // 단순히 상태를 반환하는 액션이 아닌, 특정한 값을 받는 action의 경우,
      // action.payload로 받을 수 있다(payload 고정)
      state.counter = state.counter + action.payload;
    },
  },
});

// UI컴포넌트에서 사용하기 위해 slice actions 객체 export
export const counterActions = counterSlice.actions;


export default counterSlice;

2. Store에서 다중 Slice 관리

  • 이전 포스팅에서 다뤘던 configureStore를 사용하며 combineReducer로 합친 reducers가 아닌, 각각의 slice를 reducer 필드에 넣어준다.
import {configureStore} from '@reduxjs/toolkit';
// 상태 slice가 많아져서 규모가 커지면 combineReducers를 통해 합칠 수 있지만, 
// configureStore를 사용해서 더 쉽게 구현할 수 있음.

import counterSlice from '../reducers/counter';
import showContent from '../reducers/showContent';
import authSlice from '../reducers/auth';

// 설정 객체에서 리듀서 property를 정함.
// reducer의 값이 단일 리듀서가 될 수 있음. // reducer:counterSlice.reducer
const store = configureStore({
  reducer: {counter: counterSlice.reducer, showContent: showContent.reducer, auth: authSlice.reducer},
  // confugureStore가 모든 리듀서를 큰 하나의 리듀서로 합병해줌.
});
export default store;

3. 컴포넌트에서 dispatch하기

  • useSelector, useDispatch를 사용하는 방식은 동일하다.
import {counterActions, showContentActions} from '../reducers/index';
import {useSelector, useDispatch} from 'react-redux';

const Counter = () => {
  const dispatch = useDispatch();
  // useSelector를 이용하여 리덕스가 자동으로 subscription을 설정해줌.
  // 데이터에 직접적인 변경을 가하면 안되지만 형태의 가공은 가능하다. state.showContent.slice(0,1)
  
  const {counter} = useSelector((state) => state.counter);
  const {show} = useSelector((state) => state.showContent);

  const incremnetHandler = () => {
    dispatch(counterActions.increment());
  };
  const increaseHandler = () => {
    dispatch(counterActions.increase(5)); 
    // 자동으로 액션 생성자를 생성해서 type을 전달하고, 
    // 작성한 인자를 payload(redux toolkit default)에 저장함.
  };
  const decremnetHandler = () => {
    dispatch(counterActions.decrement());
  };

  const toggleCounterHandler = () => {
    show ? dispatch(showContentActions.hide()) : dispatch(showContentActions.show());
  };

  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      {show && <div className={classes.value}>{counter}</div>}
      <div>
        <button onClick={incremnetHandler}>Increment</button>
        <button onClick={increaseHandler}>Increment BY 5</button>
        <button onClick={decremnetHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
  );
};

export default Counter;
profile
프론트엔드 개발자

0개의 댓글