React.js 리덕스 툴킷(RTK)

강정우·2023년 1월 27일
0

react.js

목록 보기
32/45
post-thumbnail
post-custom-banner

문제점

  • 앞서 포스팅 예제 코드처럼 redux의 store가 아래와 같이 짜여졌다면 몇가지의 문제점이 생긴다.
import { createStore } from "redux";

const initialState = { counter: 0, showCounter: true };

const counterReducer = (state = initialState, action) => {
  if (action.type === "increment") {
    return { counter: state.counter + 1, showCounter: state.showCounter };
  }
  if (action.type === "increase") {
    return {
      counter: state.counter + action.value,
      showCounter: state.showCounter,
    };
  }
  if (action.type === "decrement") {
    return { counter: state.counter - 1, showCounter: state.showCounter };
  }
  if (action.type === "toggle") {
    return { counter: state.counter, showCounter: !state.showCounter };
  }
  return state;
};

const store = createStore(counterReducer);

export default store;
  1. 오타 혀용 X
    • action의 값을 === 식으로 식별자를 찾아가면 리튜서는 단 하나의 오차도 허용하지 않는다.
    • 심지어 협업을 하다보면 식별자들간의 충돌이 발생할 수 있다.
  2. 무수히 많은 if문
  3. 굉장히 많은 state의 prop들
    • return문에서 반드시 initState가 갖고있는 모든 props을 선언해주어야하기 때문에 굉장히 코드가 많아진다.
  • 그래서 등장한 것이 RTK 리덕스 툴 킷이다.

Redux Tool Kit

  • RTK는 리덕스의 특징을 단순화 한 것이다. 우선 Reducer function부터 단순화해보자

import

import { createSlice, createReducer } from "@reduxjs/toolkit";
  • 위의 코드처럼 2가지의 함수가 있다. 하지만 createSlice는 한번에 여러가지를 simplify하기 때문에 createSlice를 사용하도록 하자

createSlice 사용법

import { createSlice, configureStore } from "@reduxjs/toolkit";

const initialCounterState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: "counter",
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

const initialAuthState = { isAuthenticated: false };

const authSlice = createSlice({
  name: "authentication",
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

const store = configureStore({
  reducer: { counter: counterSlice.reducer, auth: authSlice.reducer },
});

export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;
export default store;
  • 위 코드를 보면 앞서 포스팅 했던 것 과 반대로 절대 하지말라는 기존의 state에 접근하여 변경하는 행동을 하고있는데 이는 Redux toolkit과 createSlice와 같은 함수를 사용하면 괜찮다.

  • 그리고 사실 RTK가 이 코드를 캡처하고 다른 third party library인 imer를 사용하여 사용자로 하여금 state에서 직접적으로 변경하는 것 처럼 편리하게 해준다.

  • 하지만 앞서 언급했듯 실제로 기존 state 개체를 조작하는 대신, 새 state 개체를 생성하여 일부 변경 불가능한 코드로 변환되도록 한다.

  • 왜냐하면 Redux toolkit는 내부적으로 immer라는 다른 패키지를 사용하는데 위와같은 코드를 감지하고 자동으로 원래 있는 state를 복제한 후 새로운 state 객체를 생성하고 모든 state를 변경할 수 없게 유지하고, 우리가 변경한 state는 변하지 않도록 오버라이드한다.

  • 하지만 slice의 state가 많아지면 또 문제가 생길 수 있다. 왜냐면 createStore에는 하나의 reducer만 전달해야하는데 slice state가 여러개라면 state별로 reducer가 여러개가 될 수 밖에 없기 매문이다.
    그래서 있는 것이 바로 combineReducer이다.

configureStore

  • 물론 기본 redux에 리듀서를 합쳐주는 import { createStore, combineReducers } from "redux"; 구문이 있긴 하지만 RTK를 이용하여 configureStore에 대하여 알아보자
const store = createStore(counterSlice.reducer);

이게

const store = configureStore({
  reducer:counterSlice.reducer
});
  • 요러게 바뀌는데 이때 configureStore는 property로 반드시 reducer라는 단수 특징을 갖는다.
    당연하게도 Store는 앞서 포스팅 했듯 전역 state를 담당하는 하나의 리듀서 함수만 있어야하기 때문이다.

  • 그래서 지금은 1개의 state slice를 갖기 때문에 저렇게 써놔도 무방하지만 만약 여러개의 state slice를 갖는다면

const store = configureStore({
  reducer: {키값 : 슬라이스, 키값2 : 슬라이스2 ...}
});
  • 이런식으로 짤 수 있다.

action

  • 자 그럼 이제 data를 어떻게 state에 념겨줄까?
export const counterActions = counterSlice.actions;
  • 바로 슬라이스의 자동으로 생성된 property인 actions를 통하여 넘겨주면 된다. 이는 키로 가득한 객체이며 우리가 정의한 함수들이 들어있다.
  • 예를 들어 counterSlice.actions.toggleCounter()은 action 객체를 반환하는데 이게
    {type:"자동으로 반들어지는 unique한 id값"}이 반환된다.

action 사용

import { useDispatch, useSelector } from "react-redux";
import { authActions } from "../store/reduxLogix";
import classes from "./Header.module.css";

const Header = () => {
  const dispatch = useDispatch();
  const isAuth = useSelector((state) => state.auth.isAuthenticated);

  const logoutHandler = () => {
    dispatch(authActions.logout());
  };

  return (
    <header className={classes.header}>
      <h1>Redux Auth</h1>
      {isAuth && (
        <nav>
          <ul>
            <li>
              <a href="/">My Products</a>
            </li>
            <li>
              <a href="/">My Sales</a>
            </li>
            <li>
              <button onClick={logoutHandler}>Logout</button>
            </li>
          </ul>
        </nav>
      )}
    </header>
  );
};

export default Header;
  • {type:자동생성된id값, payload:매개변수값} 이런식으로 담겨서 RTK의 store로 넘어간다.
    따라서! store에 있는 action.값 => action.payload로 바꾸어주어야만 한다!

  • 또한 보면 그동안 작업해온 react는 실행하는 것이 아닌 pointing 만 하였는데 이렇게 실행을 해주는 이유는
    실제로 실행해야 하는 action creator 메서드이기 때문에 여기에서는 메서드로 실행해야 한다.
    그리고 그것들을 실행하면 action 객체를 반환한다. => action creator 자세히

코드 줄이기

  • 자 앞서 store와 action을 활용하여 state를 관리하는 법을 알아보았는데 문제는 store는 2개의 slice 조각만을 사용하고 있음에도 매우 코드가 길다 그래서 이를 잘라서 보다 편리하게 유지보수 하는 법을 알아보자.

1. 각 슬라이스 별로 .js 파일을 만든 후 initialState ~ createSlice => 한 파일로 묶는다.

이때 action export까지 함께 가지고 가야한다.

counter.js
import { createSlice } from "@reduxjs/toolkit";
const initialCounterState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: "counter",
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

export const counterActions = counterSlice.actions;
export default counterSlice.reducer;

2. 다시 store로 돌아와서 해당 slice들을 import 하여 reducer의 하나의 객체로 combind한다.

index-store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counter";
import authReducer from "./auth";

const store = configureStore({
  reducer: { counter: counterReducer, auth: authReducer },
});

export default store;
profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글