Today Sangmin Learned
article thumbnail
728x90

기본적인 Redux 사용방법에 대해서 알아봤다. 이제 이 Redux를 좀 더 편하게, 그리고 가볍게 사용할 수 있는 @reduxjs-toolkit 패키지에 대해 알아보고자 한다.

1. 다운로드

npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit

둘 중에 편한 것으로 하자. 그리고 기존에 Redux가 설치되어있는 프로젝트라면 package.json에 들어가서 Redux를 삭제하자. 왜냐면 @reduxjs-toolkit에는 Redux의 모든 게 다 들어가 있기 때문이다.

2. 적용

이제 필요한 곳에 사용해보자. 여기서는 createSliceconfigureStore을 사용할 것이다. 예시는 마찬가지로 counter이다.

이 gif를 보면 증가, 5만큼 증가, 감소, counter 토글 이렇게 네 가지의 기능이 있다. 이 네 가지를 모두 해볼 것이다. CSS는 안 한다.

1. src/store/index.js

이전 포스팅의 전체 코드에서 살짝 변경되었고 새롭게 increasetoggleCounter이 추가되었다.

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

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

const counterSlice = createSlice({
  name: "counter",
  initialState: initialState,
  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 store = configureStore({ reducer: counterSlice.reducer });

export const counterActions = counterSlice.actions;

export default store;

1) createSlice

createSlice는 전역 상태를 여러 조각으로 나누게 해 준다. 이 createSlice 내부에는 name과 초기 상태, 그리고 리듀서 이렇게 세 인자가 들어가는데 name은 하고싶은 대로, 상태값은 위에서 변수로 선언한 initialState, 그리고 리듀서가 들어간다.

const counterSlice = createSlice({
  name: "counter",
  initialState: initialState,
  reducers: {
    // ...
  }
});

여기 리듀서에는 어떤 액션을 했느냐에 따라서 메소드가 자동으로 호출되기 때문에 따로 액션이 필요하지 않다. 대신 서로 다른 리듀서들을 구별해놓고 각각의 리듀서에 해당하는 액션을 발생시키는 것이다.

2) reducers

reducers: {
  increment(state) {
    state.counter++;
  },
  decrement(state) {
    state.counter--;
  },
  increase(state, action) {
    state.counter = state.counter + action.payload;
  },
  toggleCounter(state) {
    state.showCounter = !state.showCounter;
  }
}

switch문을 사용하는 것과 비슷하다. 이전 포스팅에서 Redux가 상태를 직접적으로 변경하면 절대 안 된다고 했다. 그래서 counter라는 임의의 상태에 변경된 값을 반영하는 형태로 작성했었다. 근데 여기서는 코드만 보면 state.counter++로 실제 상태에 직접 접근해서 값을 바꾸는 것처럼 보이지만 사실은 그렇지 않다. @reduxjs-toolkit 내부의 immer 패키지가 이러한 직접적 상태 변경으로 보이는 코드를 확인하면 자동으로 기존 상태를 복제한 다음 새로운 상태 객체를 생성하며 모든 상태를 변경할 수 없게 유지한다. 그리고 이렇게 변경한 상태는 변하지 않도록 오버라이딩 한다.

3) configureStore

const store = configureStore({ reducer: counterSlice.reducer });

앱의 규모가 커졌을 때 createStore에 하나의 리듀서만 전달이 되어야 하기 때문에 앱의 규모가 커진다면, 서로 다른 slice에 접근하는 리듀서가 많아지면서 문제가 생길 수 있다.
이 때에 createStore 대신에 @reduxjs-toolkit의 configureStore를 사용하면 스토어를 만드는 건 같지만 여러 개의 리듀서를 하나의 리듀서로 합칠 수 있다는 장점이 있다. 여기서는 리듀서의 값이 단일 리듀서가 될 수 있다.

위에서처럼 counterSlice.reducer를 사용해서 모든 리듀서 메소드를 갖고 있는 counterSlice의 메소드를 가져올 수 있다.

앱의 규모가 커져서 상태 slice가 여러 개가 된다면, key값(reducer) 대신에 객체를 설정해서 그 객체 안에 원하는 대로 속성과 이름을(즉 key값을 객체로) 정하고 그 속성이 또 다른 리듀서 함수가 되게 할 수 있다. 이러한 방식으로 reducer map을 생성하는 것이다. 예시로는 { counter: counterSlice.reducer } 가 있겠다.

configureStore가 이러한 모든 리듀서들을 하나의 큰 리듀서로 병합을 해 줄 것이다.

4) actions

export const counterActions = counterSlice.actions;

createSlice로 당연히 액션을 전달할 수도 있다. counterSlice.actions.toggleCounter를 보자면 리턴값은 { type: "임의의 자동 생성된 고유한 식별자" }인데, 이걸 신경 쓸 필요는 없다. 여기서 중요한 건 createSlice의 actions key와 객체를 사용하기만 하면 된다는 것이다. export를 해줌으로써 actions와 store 모두를 export하게 된다.

2. src/components/Counter.js

우선 시작하기 전에 'react-redux' 라이브러리에서 제공하는 useSelector로 Redux의 스토어 내부의 특정 상태를 가져오며, useDispatch를 통해 dispatch를 한다는 점을 적는다. CSS는 Counter.module.css에 들어가 있고, 여기서는 다루지 않는다.

import classes from "./Counter.module.css";
import { useSelector, useDispatch } from "react-redux";
import { counterActions } from "../store/index";

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);

  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };

  const increaseHandler = () => {
    dispatch(counterActions.increase(10)); // { type: SOME_UNIQUE_IDENTIFIER, payload(Redux-toolkit이 임의로 설정함): 10 }
  };

  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };

  const toggleCounterHandler = () => {
    dispatch(counterActions.toggleCounter());
  };

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

export default Counter;

1) return문

return문을 보면 각 버튼에 대해 onClick 이벤트로 Handler 함수들을 넣었고, 실제로 적용이 될 값은 {counter}로 넣었다. 

2) useSelector

  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);

'react-redux' 라이브러리에서 제공하는 useSelector를 가져와서 store 내부 reducer의 counter과 showCounter 각각에 대해 접근하는 변수 두 개를 만들었다. counter 변수는 실제 값을 보여주는 용도이며 show는 이름과 같이 카운터 section의 가시성 여부를 결정한다.

3) handler 함수

const increaseHandler = () => {
  dispatch(counterActions.increase(5);
};

const toggleCounterHandler = () => {
  dispatch(counterAction.toggleHandler());
};

위에서 선언한 dispatch로 접근하는데, counterSlice.actions로 정의하고 export한 counterAction에 접근하였다.

increaseHandler의 경우 직접 값을 선언해줘야 한다. increase의 인자로 숫자를 넣어주면 자동으로 payload로 인식이 되기 때문에 Counter.js의 increaseHandler에서도 인자로 action.payload를 넣어주었다. 이건 하나의 규칙과 같아서 값을 전달해주기 위해서는 payload의 형태로 리듀서에 선언이 되어있어야 한다.

다음으로 toggleCounterHandler의 경우 그냥 toggleHandler 함수를 실행하게 되는데, 이렇게 되면 showCounter의 값이 이전 값과는 다른 값으로 바뀌게 된다(ex. true -> false, false -> true). 토글의 형태를 지니게 되는 것이다.

4) return문

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

각 버튼을 눌렀을 때 handler 함수가 실행되며, show의 값이 true일 때만 counter가 포함된 div가 보이도록 하였다.

이제 결과를 다시 보자. Increment를 눌렀을 때 1 증가, Increment by 5를 눌렀을 때 5 증가, Toggle Counter를 눌렀을 때 useSelector(const counter = useSelector((state) => state.counter);)로 가져왔던 counter가 사라졌다. 이런 식으로 Redux를 사용할 수 있다.

'Web > Redux' 카테고리의 다른 글

[Redux] Redux의 기본 흐름  (0) 2022.01.12
[Redux] 리덕스 라이브러리 이해하기(+Flux)  (0) 2021.02.27
profile

Today Sangmin Learned

@steadily-worked

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!