상태관리 레인저! 프론트엔드 상태관리 라이브러리들을 살펴보자 #2 (Redux)

나의 개발 일지·2023년 8월 16일
0

지난시간에는 간단히 상태관리의 개념과 배경 그리고 몇몇 라이브러리들의 이름을 소개했다. 이번 시간에는 상태관리 라이브러리의 터줏대감들인 Redux 와 MobX를 살펴보는 시간을 가져보도록 하자.

들어가며...

이전 시간에 소개한대로 Flux 패턴이란 개념이 등장하고 나서 이러한 방식의 프로그래밍 설계 패턴을 도입하여 프론트엔드의 상태를 보다 관리하기 쉽도록 도와줄 수 있는 라이브러리들이 등장하기 시작했다. 현재의 상태관리 라이브러리들의 수가 꽤나 많게 느껴질지 모르겠지만, Redux (2015) 는 대부분의 라이브러리들보다 앞서 등장해 선두를 차지했다. 꽤나 오래전에 등장한 것처럼 느껴질지도 모르겠지만, Redux는 현재 까지도 계속 잘 관리되어지고 있으며 또 많은 사람들이 계속 사용하고 있는 라이브러리들이다. Redux는 또한 다양한 미들웨어를 지원하고 더 Redux를 사용하기 쉽도록 Redux-toolkit을 개발하는 등의 노력을 통해 프론트엔드 상태관리 경쟁에서 대부분의 라이브러리들을 아직까지 압도하고 있다. 그렇다면 지금부터 이 상태관리의 대명사라고 할 수 있는 Redux에 대해서 알아보도록 하자.


상태관리의 대명사, Redux

Redux의 개념

Redux is a pattern and library for managing and updating application state, using events called "actions". It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.


Redux는 어플리케이션의 "actions"라고 불리는 이벤트를 사용하여, state를 업데이트하고 관리하는 하나의 패턴이자 라이브러리입니다. Redux는 state가 정확히 예측가능한 때에 업데이트하도록 만들어주는 방법들을 통해, 당신의 어플리케이션 전반에서 사용될 state들의 중앙 저장소 같은 역할을 할 수 있습니다.

Redux의 공식문서에서는 Redux가 무엇인지 위와 같이 간단히 소개하고 있다. 그런데 보다보면 눈에 띄는 단어들이 몇몇 보이는 것 같다. "Actions", "Store"... 그렇다, Redux는 이전에 언급했던 Flux 패턴을 통해 자신들의 상태관리 방식을 구현해냈다.

Redux는 Flux 패턴의 뼈대를 가지고와 이를 프론트엔드의 상황에 적합하도록 직관적으로 만들어 사용한다. Flux 패턴의 데이터 일방향성의 특징도 역시 그대로 흡수해왔다. 또한 Redux는 State의 불변성(Immutablity)을 유지하는 방식으로 UI 데이터의 로직을 좀 더 순수하게 유지할 수 있도록 도와준다. 다만, Flux를 패턴을 그대로 적용시켜 사용하지 않고 살짝 변경하는 방법으로 이를 구현했다.

Redux는 Flux 패턴에서의 Dispatcher를 그대로 사용하지 않고 Reducer의 개념을 도입했다. 이러한 방식을 도입함으로써, Redux는 이벤트에 UI 변경 로직들을 바인딩하는 것이 아니라, 변경 로직들을 모두 순수함수로 구성해 Reducer라고 불리는 곳에 모아 두어 한 번에 관리하면서 보다 편리하게 사용할 수 있게 만들어주었다.

이외에 Dispatch의 개념이 View 에서 Action 객체를 Reducer로 전달하는 부분을 말하는 것으로 조금 바뀐 것을 제외하고 대부분의 부분들은 Flux 디자인 패턴을 따르고 있다.

Redux의 개념을 간단하게 알아보았으니 직접적인 코드를 통해서 조금 더 자세하게 알아보도록 하자.



Redux의 사용

Redux의 설치

Redux는 라이브러리 패키지로 제공된다. 패키지 매니저로 redux와 React 환경에서 사용될 react-redux 패키지를 설치하도록 하자

redux react-redux

Redux의 사용

Redux는 보통 이러한 방식을 통해 사용된다.

  1. 어플리케이션의 여러 곳에서 사용될 만한 states와 로직을 추려 Store를 만들 준비를 한다.
  2. 추려진 상태들을 Reducer 함수를 통해 사용할 수 있도록 하나의 객체(앞으로 만들 store의 State로 활용)로 만들어 initalState를 작성한다.
  3. 로직을 기능 별로 분류하여 Reducer 함수 안에 Switch 문을 통해 작성하고 initialState를 전달한다.
  4. Redux 라이브러리의 createStore 함수에 Reducer를 전달하여 store를 생성한다.
  5. createStore 함수가 반환하는 store와 store의 getState 메소드와 dispatch 메소드를 통해 store를 활용해 state 값을 읽어오거나 변경한다.
  6. Action 이 발생해 state 값을 변경할 일이 발생하면, 미리 작성해둔 Reducer 함수 안의 switch 문에 적절하도록 action 객체를 생성해 dispatch 함수 안에 전달한다.

React의 환경에서 Redux를 사용한다는 전제 하에 간단한 Todo 어플리케이션을 작성해보았다.





미리 만들어진 5개의 투두를 가지고 있는 투두 리스트를 만들어 보았다. 앞으로 모든 라이브러리를 소개하면서 이 녀석을 바탕으로 비교하고 설명해볼 것이다. 대단한 기능이 존재하지는 않고 새로운 투두를 만들거나, 체크하거나, 삭제할 수 있다.

1. State와 로직을 준비한다.

간단한 어플리케이션이니 만큼 이 부분에서 크게 고려할 사항은 존재하지 않을 것이다. todos 데이터는 각각 id, content, complete 에 대한 정보를 가지고 있고 todos.ts 파일에서 작성되어 있다. 그리고 이 todos에 대한 데이터를 투두 항목을 렌더링 하는 TodoList.tsx, 투두 리스트들을 한 번에 렌더링 하는TodoLists.tsx, 새로운 투두를 입력하도록 도와주는 Foooter.tsx 세 가지 컴포넌트에서 동시에 불러와 사용하고 있다.

만약, Redux를 사용하지 않고 props를 통해 내려보내야 했다면 늘어나는 컴포넌트의 가짓수 만큼이나 많은 반복작업을 했어야 했을 것이다. 하지만 Redux를 사용하면 단 한번에 작업으로 모든 컴포넌트가 이를 공유하면서 사용할 수 있다.

앞서 말했다시피 어플리케이션이라고 부르기도 뭐할만큼 간단한 로직을 가지고 있기에 이 단계에서 그다지 많은 노력이 필요하지는 않는다. 하지만 로직이 방대해지고 실제 어플리케이션에 가까워질수록 어떠한 로직과 상태를 Redux와 같은 상태 관리로 관리해야할지 조금 고민해보아야 할 것이다. 어쩌면 필요하지 않은 과정들을 더 늘리는 것일 수도 있기 때문이다.

일반적으로 1. 여러(2~3개 이상) 컴포넌트에서 공통으로 사용하게 될 상태이거나, 2. 렌더링 트리의 너무 깊은 곳(2~3단계 이상)에 위치해 거쳐가야하는 단계가 많은 경우 상태 관리를 고려하기 시작한다.

2. Reducer와 InitialState를 작성하며 Redux store의 초기 설정하기

상태관리 사용의 판단이 분명해졌다면, 이제 라이브러리를 사용하기 위한 설정을 준비하도록 하자. Redux를 사용하기 위해서는 store 가 필요하고, store를 사용하기 위해서는 ReducerInitialState 가 필요하다. 이번 단계는 이 Reducer 와 InitialState 를 작성하는 단계이다.

Redux에서 Reducer가 무엇인지에 대해서 간단하게 위에서 짚고 넘어갔었다. 이제 Reducer를 작성해야하니 조금 더 자세히 살펴보자.

Reducer는 Flux 패턴에서 Dispatcher 가 하는 역할을 모두 담당하는 하나의 함수이다. View 에 필요한 state 들과 이 state 들을 변경하게 될 로직들을 모두 이 Reducer에 안에 작성한다. Reducer는 하나의 관심 영역(domain)에 대한 다양한 로직을 처리하는 것이 기본 목적이기 때문에 switch - case 문을 사용해서 각각의 로직의 분기를 처리한다. (Reducer는 자바스크립트의 Array.reduce 함수에서 그 이름을 따왔다고 한다. reduce 함수가 모든 값들을 모아 결국 하나로 만드는 것처럼, Reducer도 비슷하게 로직들의 많은 기능이 하나로 모아져 Reducer가 된다고 생각하면 좋을 것이다.)

자 그렇다면 Reducer 함수를 어떻게 작성하는 것이 좋을까? 먼저 Reducer 함수로 전달되어지는 인자들과 return 값을 먼저 살펴보도록 하자. Reducer 함수는 다음과 같은 인자들을 전달 받는다.

  1. state : Redux store에서 사용하게될 state 객체의 기본형태를 정의하여 전달한다고 생각하면 된다. 기본적으로 자바스크립트 객체 형태를 띄며 1번 단계에서 생각한 state들을 이 객체에 담아 사용한다. Reducer 함수는 작동 될때마다 여기로 전달될 state를 로직을 처리하는데 사용할 것이다. 위에 언급한 initialState의 값을 작성하여 보통 default 값으로 사용한다.

  2. action : Redux를 설명하면서 사용했던 그림의 Action 객체이다. action 객체는 기본적으로 유저 인터랙션의 종류와 그에 대해 실행된 로직에 추가 값을 프로퍼티로 갖는 객체이다. action 객체의 타입은 사용자가 원하는대로 지정할 수 있겠지만, 정해진 컨벤션이 존재한다. 보통 다음과 같은 형태로 사용한다.

type action = {
	type: string;
   // 사용자 인터랙션의 종류, state분류/action종류 형식으로 작성한다.
    payload?: any;
  // 로직을 수행할 때 필요한 값들을 추가적으로 전달할 수 있는 프로퍼티이다.
  // string 뿐만 아니라 어떠한 값을 할당해도 상관없다. (any로 타입을 할당하지는 말도록 하자)
}

그리고 Reducer 함수는 다음과 같은 값을 return 한다.

  • Return : Reducer안의 분기들의 로직의 계산이 끝나면 새로운 state 값을 반환해야한다. 이 state 값은 Redux store에서 새로운 state로 사용되어서 View에서 읽게 될 것이다. default의 경우에는 인자로 전달된 state를 그대로 반환한다.

이 부분이 준비되었다면 이제 Reducer 함수를 작성해보도록 하자. 위에서 설명한 두 인자들을 바탕으로 switch case문을 통해 state 변경 로직들을 작성하면 된다. 하지만 작성 규칙들을 기억해야한다.

1. state 값을 직접 변경하지 않는다. 반드시 복사를 통해서 새로운 state값이 할당 되도록 로직을 구성해야한다.

2. Reducer 안에서 side effect를 발생시키는 로직을 작성하지 않도록 한다.

나는 이를 통해 위 투두리스트의 reducer를 다음과 같이 작성하였다.

이렇게 되면 store를 만들 준비를 마친 것이다.

import { todos, type todo } from "../../todos";

const initialState = todos;
// [{id: 1, content: "wake up", completed: false}...]

export type action = {
  type: "todos/add" | "todos/complete" | "todos/remove";
  payload: Partial<todo>;
};

export default function todosReducer(state = initialState, action: action) {
  switch (action.type) {
    case "todos/add": {
      const newTodo = { ...action.payload, id: state.todos.length + 1 };
      return {
        todos: [...state.todos, newTodo]
      };
    }

    case "todos/complete": {
      const selectedId = action.payload.id;

      return {
        todos: state.todos.map((item) => {
          if (item.id === selectedId) {
            item.completed = !item.completed;
          }
          return item;
        })
      };
    }

    case "todos/remove": {
      const selectedId = action.payload.id as number;
      return {
        todos: [
          ...state.todos.slice(0, selectedId - 1),
          ...state.todos.slice(selectedId)
        ]
      };
    }

    default:
      return state;
  }
}


3. store 생성하기, Redux 환경 구축하기 그리고 사용하기

이제 store를 만들 준비가 끝났으니 본격적으로 Redux를 활용하기 위해서 store를 생성해보도록 하자.

store는 Redux 상태관리의 근본이 되는 개념이다. 우리가 만든 Reducer와 State를 모두 store에서 관리하고 store에서 나오는 method 들을 통해서 사용자의 인터랙션들과 상호작용하여 View를 변경시킬 수 있다.

store를 생성하는 방법은 간단하다. Redux 라이브러리의 createStore 함수에 우리가 만들어주었던 reducer를 전달하기만 하면 Redux store를 생성해 사용할 수 있다.

// store.ts

import { createStore } from "redux";
import todosReducer from "./reducer/todosReducer";

const store = createStore(todosReducer);

export default store;

createStore 함수는 3 가지 인자를 전달받을 수 있다.

  1. reducer: 우리가 이전 단계에서 만들었던 Reducer 함수를 의미한다. 이를 전달함으로써 store를 생성할 수 있다.

  2. preloadedState: Reducer함수를 작성하면서 initialState를 만들지 않았다면, 이 인자 값으로 전달해 store를 생성하면서 state를 할당해 줄 수 있다.

  3. enhancer : store의 메소드들을 래핑(wrapping)하는 함수를 전달받을 수 있다. 이러한 함수를 전달함으로써 Redux의 dispatch->action->Reducer 로 이루어지는 흐름에 원하는 Middleware를 파이프라인으로 연결해 사용할 수 있도록 만들어준다. Middleware로 여러가지를 할 수 있겠지만 가장 중요한 것은 side-effect를 발생시키는 코드를 사용할 수 있다는 것이다. (이러한 상태관리 라이브러리들의 Middleware들을 나중에 한 번에 모아서 살펴보도록 하자.)

우리 같은 경우는 reducer와 initialState를 미리 작성해두었으니 createStore 함수에 Reducer 값만을 전달하면 store가 생성된다.

store는 다음과 같은 메소드들을 제공한다.

  1. getState: (state) => state.property 와 같은 형태의 콜백을 전달할 수 있으며, 콜백이 리턴하는 store의 state 값을 반환해준다. 한 마디로 state 값을 읽어와 사용할 수 있게 해준다.

  2. dispatch : 위에서 설명한 action 객체를 전달받아, Reducer 함수를 동작시키는 역할을 한다.

  3. subscribe : Redux store를 구독하도록 만들어 주며, 콜백을 전달받아 Reducer 함수가 동작하면 콜백을 실행시키도록 만들어준다. subscribe 함수는 구독상태를 해제하게 해주는 unsubscribe 함수를 반환한다.

프레임워크를 사용하지 않는 환경의 경우, store의 getState 함수를 통해 state값을 받아와 필요한 곳에 전달해주고, subscribe 메소드와 dispatch 메소드를 통해서 원하는 때에 dispatch 메소드에 action 객체를 전달해 호출하여 state를 변경하거나 subscribe안에 콜백으로 실행시킬 함수를 전달하게 되면 Redux 의 간단한 사용은 끝이난다.

하지만 React 환경에서 Redux를 사용하기 위해서는 다음과 같은 추가 과정이 필요하다.

  1. 리액트 렌더링 트리내에 Redux store를 위치 시켜 필요한 컴포넌트에서 값을 읽어 올 수 있게 해야한다.

  2. store 메소드가 아니라, Hooks(useSelector, useDispatch)를 통해 Redux store를 사용해 React와 Redux를 동기화 시킨다.

이러한 React 환경에서 Redux 사용을 가능하게 만들어주는 'react-redux' 패키지를 설치한 후에 다음과 같이 설정하면 바닐라환경에서 처럼 React에서도 Redux를 사용할 수 있게 된다.

// App.tsx (App.tsx 가 아니더라도 Redux store를 위치시킬 렌더링 트리가 있는 곳)
import store from "./store/store";
import TodoLists from "./components/TodoLists";
import Footer from "./components/Footer";

// react-redux 패키지에서 store를 렌더링 트리내에 위치시킬 수 있도록 하는 역할을 한다.
import { Provider } from "react-redux";

export default function App() {
  return (
    <div className="App">
      <h1>Make Todos with Redux</h1>
    // 실제로 store를 사용하게 될 위치에 Provider를 위치시키고 store를 넣어주었다.
      <Provider store={store}>
        <TodoLists />
        <Footer />
      </Provider>
    </div>
  );
}

// TodoLists.tsx / (getState를 사용하는 곳)
import type { todo } from "../todos";
import TodoList from "./TodoList";

// store.getState 메소드와 동일한 역할을 하는 React 커스텀훅이다.
import { useSelector } from "react-redux";

const TodoLists = () => {
  const todos = useSelector<{ todos: todo[] }, todo[]>((state) => state.todos);
 ...
}

// TodoList.tsx / (dispatch를 사용하는 곳)
import type { todo } from "../todos";
// store.dispatch 메소드와 동일한 역할을 하는 함수를 리턴해준다.
import { useDispatch } from "react-redux";

const TodoList = ({ content, id, completed }: todo) => {
  const dispatch = useDispatch();
  
	
  return (
  ...
    <button 
    // 생성된 dispatch 함수를 action 객체를 전달해 사용한다.
    onClick={() => dispatch({ type: "todos/complete", payload: {id}})}>
         Check
       </button>
  )

}

잠깐! 혹시 그냥 넘어가려고 하는 당신, Reducer에서도 그러했던 것처럼 store를 생성할 때도 역시 규칙이 존재한다.

한 어플리케이션에서 store는 단 하나만 만들 수 있으며, store에는 단 하나의 reducer만 전달할 수 있다. 그리고 이를 rootReducer라 한다.

그런데 만약 우리가 한 영역(domain)에 관련된 reducer가 아니라 여러 영역에 걸쳐 사용하기 위해 reducer를 분할한다면 어떻게 할까?

이럴 때를 위해서 Redux는 다음과 같은 Reducer composition 이라는 API들을 준비해두었다.

3.5 Reducer composition

여러 영역에 관해 store를 만들어 사용하고 싶다면 그 상황에 대응하도록 state를 구성하고 Reducer를 여러개 만들면 된다.

잠깐, 아까 reducer는 하나만 만들어야 한다고 하지 않았나?

맞다, Reducer의 입구가 단 하나이기만 하면 된다. 즉, 하나의 입구역할을 할 Reducer가 하나 존재하고 그 하위에서 실질적으로 Reducer 로직을 실행시킬 Reducer들은 여러 개가 있어도 좋다는 것이다. 이러한 것을 Reducer composition 이라고 부르고 하나의 입구 역할을 하는 Reducer를 rootReducer라고 부르며 각각 역할을 하게되는 Reducer들을 slice라고 부른다. (이러한 용어와 개념은 기억해두자 다른 라이브러리에서도 비슷하게 등장하기 때문이다.)

그렇다면 어떻게 하면 이러한 Reducer composition을 사용할 수 있는 것일까? 사실은 꽤나 간단하다.

1. state 프로퍼티에 맞게 reducer들을 작성하고, rootReducer내에서 이들을 프로퍼티에 할당한다.

// 이 코드는 예제코드로만 작성했다.
// ../reducers/reducer
import aReducer from "../reducers/aSlice";
import bReducer from "../reducers/bSlice";

type ExampleState = {
	a: string;
    b: string;
}

const rootReducer = (state:ExampleState, action) => {
	return {
    	a: aReducer,
        b: bReducer,
    }
}

export default rootReducer

2. 귀찮은가? 그렇다면 더 간단한 방법이 있다.

// 이 코드는 예제코드로만 작성했다.
// ../reducers/reducer
import {combineReducers} from "redux"
import aReducer from "../reducers/aSlice";
import bReducer from "../reducers/bSlice";

type ExampleState = {
	a: string;
    b: string;
}

const rootReducer = combineReducers({a: aReducer, b: bReducer});

export default rootReducer

끝이다. 이러한 방식으로 Reducer를 쪼개어 원하는 만큼 분리해 사용할 수 있다.


Redux Toolkit

이렇게 Redux의 기본적인 사용방법을 알아보았다. 그런데 지금까지 이야기한 것들을 읽으면서 한 가지 느낀점이 있을지도 모르겠다. 그렇다 상태 관리를 사용할 수 있도록 도와주는 것은 좋은데, 이를 위해 거치는 단계가 너무나 많다는 것이다.

Redux는 프론트엔드 상태관리에서 대표주자로 자리매김했지만 이렇게 Redux를 사용하기 위해서 익숙해져야하고 거쳐야하는 단계가 많은 것은 Redux를 사용하는 이들에게 자꾸 짐을 안겨주었다. 실제로 Redux는 이렇게 간단한 기능을 위해서 필요하지 않다. 어플리케이션이 굉장히 비대해져서, 수 많은 View와 이에 연결된 상태들을 관리하고 분배하기가 너무 힘들 때 필요성이 발생한다. 더하여 Redux를 사용하면서 Redux의 다양한 Middleware를 사용하는 것은 Redux를 사용할 때 빼놓을 수 없는 부분이기 때문에, 이러한 모든 것들이 합쳐지면 Redux를 위한 코드량이 점점 비대해지는 배보다 배꼽이 더 큰 상황이 되어버렸다.

Redux는 실제로 이러한 부분에서 악명이 높았다. 그래서 Redux 팀은 위와 같은 보일러플레이트 코드를 확실하게 줄이고 Redux를 보다 편하게 사용할 수 있도록 패키지를 개선해 선보였는데, 그것이 바로 Redux-Toolkit 이다.

사실 위에서 부터 굉장히 길게 설명해 내려왔지만 이러한 방식은 레거시한 방식이고 공식문서에서 조차 이 방법이 아니라 Redux Toolkit을 통해 Redux 설정을 할 것을 강력하게 권고하고 있다. 위와 같이 레거시한 방법을 굳이 소개한 이유는 Redux에 대해서 이해하게 되면 다른 상태관리 라이브러리도 같이 이해하는데 도움이 될 것이라고 생각했기 때문이다.

어쨌든 쓸데 없는 소리는 이정도로 하도록 하고, 그렇다면 Redux Toolkit을 통해 Redux를 보다 간단히 사용할 수 있는지 알아보자

Redux Toolkit의 설치

먼저 Redux Toolkit을 사용하기 위해서는 Redux와 연계된 다른 패키지가 필요하다.

// 패키지매니저를 통해 이를 설치하도록 하자

@reduxjs/toolkit

Redux Toolkit의 사용

Redux를 사용하는 방법을 미리 살펴보고 왔으니, 조금 간단하게 간추려서 설명하도록 하겠다. Redux Toolkit이 Redux의 설계까지 모두 뒤집어엎는 새로운 방식을 제시하는 것은 아니기 때문이다.

큰 개념에서는 기존과 동일하다. 하지만 선언하는 방식에 차이가 있다.

  1. createStore를 통해 store를 생성하지 않는다. 대신 configureStore라는 함수를 사용한다. 비슷할 수 있겠지만 다음을 통해 보다 직관적인 기능을 제공한다.
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  
  reducer: { 이곳에 reducer를 선언할 수 있다.	},		  
  middleware?: // Redux 미들웨어들을 배열에 담아 전달할 수 있다
  devTools? : // Redux는 자체 Devtools를 지원하는데 이 기능의 여부이다.
  preloadedState? : // createStore의 그것과 같다,
  enhancers? : // Redux 미들웨어가 아닌 커스텀 enhancer를 전달할 수 있다.
                                    
})
  1. configureStore 함수에 이전과 같이 Reducer를 전달해야한다. 다만 조금 다른 방법을 사용할 수도 있다. 아까 이야기했던 slice가 생각이 나는가? Redux Toolkit은 slice 패턴을 보다 쉽고 빠르게 사용할 수 있게 만들어주어 여러가지 Reducer들을 간편하게 다룰 수 있도록 도와준다.
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

// createSlice 함수를 통해서 sliceReducer를 쉽게 만들어낼 수 있다.
// slice를 원하지 않는다면 createReducer 함수를 통해서 만들면 된다.
export const counterSlice = createSlice({
  name: 'counter', // 내부적으로 slice를 정의하고 관련된 action을 생성할 때 사용한다
  initialState, // initialState
  reducers: // 우리가 이전에 switch-case문으로 정의했던 Reducer의 로직을 다음과 같이 바꿀 수 있다. 
  {
    increment: (state) => {
      state.value += 1 
      // 심지어 내부적으로 불변성을 보장해주는 Immer 라이브러리를 사용하고 있기 때문에
      // Reducer 로직을 작성할 때, 끊임 없이 복사를 하지 않아도 된다!
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

// 보면 알겠지만, Reducer의 함수들과 같은 이름을 한 함수들을 반환해준다.
// 이들은 action creator 라는 함수들로 자동적으로 action 객체들을 생성해주는 역할을 한다.
// 일일히 action 객체를 손으로 생성하지 않아도 되는 것이다.
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
  1. 그리고 이렇게 생성한 slice들을 위의 configureStore 함수의 reducer 패러미터에 객체 형태로 전달하게 되면 rootReducer 설정도 금방 마칠 수 있다.
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

// type infering 까지 설정해 사용할 수 있다.
// state의 타입을 infer 하고있다.
export type RootState = ReturnType<typeof store.getState>
// dispatch 함수의 타입을 infer 한다.
export type AppDispatch = typeof store.dispatch
  1. 이후에 사용하는 방법은 위와 동일하다. React를 사용할 경우 React 설정까지 마쳐준 후에 준비된 Hooks (useSelection, useDispatch) 를 사용하여 store에 접근하고 store를 향해 dispatch 할 수 있다.
import type { RootState } from '../../app/store'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
  const count = useSelector((state: RootState) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}



마치며...

이렇게 Redux와 Redux Toolkit의 개념과 사용법을 간단히 알아보았다. Redux는 프론트엔드 상태관리 라이브러리의 선두주자로 처음 부터 끝까지 그 자리를 굳건히 지키고 있는 만큼 Redux에 대해서 살펴보는 것이 상태관리 라이브러리 전반을 이해하는 데에 도움이 될 거라고 생각했다. 이를 통해서 첫 번째 포스팅 보다는 조금 더 상태관리에 대해서 이해할 수 있는 시간이 되었으면 좋겠다.

지금 소개한 내용들은 단순히 Redux를 사용하는 간단한 방법에 초점이 맞추어져 있다. 하지만 Redux는 Middleware와 enhancer, devtools 등을 자유자재로 사용할 때 그 힘을 제대로 발휘할 수 있다. Redux의 대표적인 라이브러리 몇몇은 이후의 시간에서 간단히 알아볼테지만 추후에 Redux와 그 Middleware들에 대해서 좀 더 자세히 알아보는 시간을 가질 수 있었으면 좋겠다. (관심이 있는 사람들은 Redux 와 Redux Toolkit의 공식문서를 읽어보도록 하자)

profile
반갑습니다! 오늘도 좋은 하루 입니다🙋🏻‍♂️. 프론트엔드 개발을 공부하고 있습니다!

5개의 댓글

comment-user-thumbnail
2023년 8월 20일

리덕스 툴킷 써보면 확실히 그 전으로 돌아가기 힘든 것 같습니다ㅎㅎ 잘 읽었습니다!

답글 달기
comment-user-thumbnail
2023년 9월 10일

제대로 연환님 블로깅 보는건 처음인 것 같은데 내용 너무 깔끔하고 좋네요 !!
리덕스 몇 번 써보고 잊게되는데 사용할 때마다 참고하기 좋은 것 같아요 감사합니다

답글 달기
comment-user-thumbnail
2023년 9월 10일

1부터 10까지 한 글에 모아두셨네요,, 기록력 미쳤습니다! 잘보고 갑니다 ㅎㅎ 현업에서도 사용하고 있어 흥미롭게 봤습니다 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 9월 10일

기본적인 부분을 세세히 적어주셔서 도움이 많이 됬습니다! 휘리릭하고 읽어서 그런지 몰랐던 부분도 알고갑니당!

답글 달기
comment-user-thumbnail
2023년 9월 10일

리덕스 툴킷을 사용해보지 않은자 리덕스를 논하지마라..느낌인 것 같던데 .. ㅎㅎㅎ 저도 언젠간 사용할 때 참고하기 좋게 너무 잘 써주신 것 같아요~!!

답글 달기