리덕스 vs 리덕스 툴킷

리덕스를 좀 더 쉽게 사용하기 위해 만들어 진 것이 리덕스 툴킷이다.

주요 차이점은 다음과 같다.

  • Redux DevTool Extension을 명시적으로 설정해야 한다. => 자동으로 지원된다.
  • state를 immuable하게 직접 관리해야 한다. => Immer.js를 이용해 자동으로 관리된다.
  • store를 설정하는 것이 복잡하다. => store설정하는 것이 훨씬 쉽고 미들웨어가 내장되어 있다.
  • reducers와 creators를 각각 만들어야 한다. => 액션 생성과 리듀서 생성을 하나의 함수로 대체한 createSlice 함수가 있다.
  • 비동기적인 requests 작업을 할때 많은 코드가 필요하다. => createAsyncThunk 함수가 비동기적 requests를 처리한다.

기본 파일 구조

  • src
    • app
      • store.js
    • features
      • counter
        • Counter.jsx
        • counterSlice.js
    • App.js
    • index.js

store 생성

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";

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

기능별로 리듀서를 나누어 store를 생성할 수 있다.
기존의 redux에서는 combineReducers함수를 이용해서 설정해야 했었는데 간편해졌다.

App level에 store 적용

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { store } from "./app/store";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

react-redux에서 지원하는 Provider 컴포넌트를 최상위 컴포넌트에 위치시켜서 앱 내에서 store가 공유되게 한다.

counterSlice 생성

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

const initialState = {
  count: 0,
};

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
    reset: (state) => {
      state.count = 0;
    },
    incrementByAmount: (state, action) => {
      state.count += action.payload;
    },
  },
});

export const { increment, decrement, reset, incrementByAmount } =
  counterSlice.actions;
export default counterSlice.reducer;

상태와 액션 타입과 리듀서를 합친 모양새다.
슬라이스는 저장소의 일부로 보면 될 것 같다.

해당 슬라이스의 이름은 "counter" 로 설정했고 prefix로 이용된다고 한다.

해당 슬라이스의 초기값은 initialState로 설정된다.

reducers 속성은 필수 속성인데, 복수형이라는 것에 주의하자.

기존의 리듀서에서 액션타입에 따른 if else구문 또는 switch구문 대신에 각각의 액션 타입에 대한 로직을 함수별로 분리했다.

dispatch 함수에 액션을 넣어주는 대신 reducers 객체내에 있는 함수와 같은 이름의 함수를 호출값을 매개변수로 전달한다.
예: dispatch({type:"ADD", payload}) 대신 dispatch(increment())

reducers 객체내의 함수와 같은 이름의 함수는 슬라이스의 actions 속성에 나열되어 있다.
슬라이스의 reducer 속성은 해당 슬라이스 내부의 reducers를 하나로 통합한 것이다.
이는 store를 만드는 configureStore에서 사용된다.

Counter 컴포넌트

import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, reset, incrementByAmount } from "./counterSlice";
import { useState } from "react";

const Counter = () => {
  const count = useSelector((state) => state.counterStore.count);
  const dispatch = useDispatch();

  const [incrementAmount, setIncrementAmount] = useState(0);
  const addValue = Number(incrementAmount) || 0;
  const resetAll = () => {
    setIncrementAmount(0);
    dispatch(reset());
  };
  return (
    <section>
      <p>{count}</p>
      <div>
        <button onClick={() => dispatch(increment())}>+</button>
        <button onClick={() => dispatch(decrement())}>-</button>
      </div>
      <input
        type="text"
        value={incrementAmount}
        onChange={(e) => setIncrementAmount(e.target.value)}
      />
      <div>
        <button onClick={() => dispatch(incrementByAmount(addValue))}>
          Add Amount
        </button>
        <button onClick={() => resetAll()}>reset</button>
      </div>
    </section>
  );
};

export default Counter;

store 내부의 값을 가져올 때는 store를 정의할때 사용한 reducer내부의 키값을 이용해 접근한다.

dispatch를 할 때는 해당 슬라이스의 리듀서 함수를 호출한다.
또한 리듀서 함수의 매개변수에 값을 전달하면 이 값은 리듀서 함수 내부에 action.payload 와 매핑된다.

App 컴포넌트

import Counter from "./features/counter/Counter";

function App() {
  return (
    <main className="App">
      <Counter />
    </main>
  );
}

export default App;

결과물

profile
프론트에_가까운_풀스택_개발자

0개의 댓글

Powered by GraphCDN, the GraphQL CDN