[리액트] redux toolkit (RTK) 사용법

navyjeongs·2022년 8월 3일
2

리액트

목록 보기
2/8
post-thumbnail

redux의 단점

  • redux의 store는 매우 복잡하다.
  • redux를 유용하게 사용하기 위해 매우 많은 패키지 설치가 필요하다.
  • 리덕스는 매우 많은 보일러 플레이트 코드를 필요로 한다.

redux-toolkit(RTK)는 이러한 문제점을 개선시켰다.


redux-toolkit에 포함된 API들 중 일부 설명

  1. configureStore()

    • reducer들을 자동적으로 하나로 합칠 수 있다.
  2. createSlice()

    • reducer를 만들어준다.
    • name, initialState, reducers의 field를 포함해야한다.
    • immer를 내장하고 있어 불변성 관리에 용이하다.
      -> 몇 가지의 규칙을 지키기만 하면 된다. 해당 규칙은 글 가장 아래에 설명되어 있다.

redux-toolkit 설치하기

  • 터미널에 npm i redux react-redux @reduxjs/toolkit를 입력한다.

내가 만들 것

  • 숫자를 count하는 버튼과 이름을 추가하고 삭제하는 버튼을 redux-toolkit을 이용하여 만들어보겠다.


1. 카운터 만들기

1) store 폴더에 store.js 파일을 생성한다.

  • store.js에서 configureStore를 만든다.
import { configureStore } from "@reduxjs/toolkit";

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

export default store;

2) index.js에서 App 컴포넌트를 Provider로 감싸고 store prop을 전달한다.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";import { createSlice } from "@reduxjs/toolkit";
import store from "./store/store";

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

3) counterSlice.js를 만든다.

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

// 초기값 생성
const initialState = {
  value: 0,
};

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    PLUS_ONE: (state) => {
      state.value += 1;
    },
    MINUS_ONE: (state) => {
      state.value -= 1;
    },
    PLUS_AMOUNT: (state, action) => {
      state.value += action.payload;
    },
  },
});

// Counter.js에서 dispatch()하기 위해 actions를 export
export const { PLUS_ONE, MINUS_ONE, PLUS_AMOUNT } = counterSlice.actions;

// store.js의 reducer에 작성하기 위해 reducer를 export
export default counterSlice.reducer;
  • counterSlice에는 다음와 같은 내용이 저장되어 있다.

4) store.js에서 만든 reducer를 작성한다.

import { configureStore } from "@reduxjs/toolkit";
// counterSlice.js에서 reducer를 default로 export했으므로 원하는 이름으로 import 할 수 있다.
import counterReducer from "../reducer/counterSlice.js";

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

export default store;

5) 만든 리듀서를 사용할 Counter.js를 작성한다.

import { useSelector, useDispatch } from "react-redux";
// counterSlice에서 name export한 actino들을 import한다.
import { PLUS_ONE, MINUS_ONE, PLUS_AMOUNT } from "../reducer/counterSlice";

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

  return (
    <div>
      <h1>현재 count 값 : {count}</h1>
      <button onClick={() => dispatch(PLUS_ONE())}>+1</button>
      <button onClick={() => dispatch(MINUS_ONE())}>-1</button>
      <button onClick={() => dispatch(PLUS_AMOUNT(5))}>+5</button>
    </div>
  );
};
export default Counter;
  • 이 때 useSelector를 사용하여 현재 리덕스 상태를 조회한다. 이때 state에는 다음과 같은 내용이 저장되어 있다.
    • 또한 useSelector는 리덕스 상태를 조회 하여 상태가 바뀌지 않았다면 리렌더링 하지 않는다.
  • 여기서 counter는 store.js에서 설정한 reducer의 이름이다. 만약 counter.js에서 reducer이름을 다음과 같이 countcount로 변경해본다.
const store = configureStore({
  reducer: {
    countcount: counterReducer,
  },
});
  • 그러면 state에는 다음과 같은 내용이 저장되어 있다.

6) App.js에 Counter 컴포넌트를 추가한다.

import Counter from "./counter/Counter";

function App() {
  return (
    <div>
      <Counter />
    </div>
  );
}
export default App;

7) counter는 완성되었다.

2. 배열에 이름 추가 및 삭제

1) 앞서 초기 설정은 다 되어 있으므로 reducer를 추가하기만 하면 된다.

2) reducer폴더에 mangeSlicer.js파일을 생성한다.

  • 이 때 immer가 내장되어 있으므로 불변성을 상관하지 않아도 된다!
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  name: ["Kim", "Lee", "Park"],
};

const manageSlicer = createSlice({
  name: "mange",
  initialState,
  reducers: {
    ADD_NAME: (state, action) => {
      state.name.push(action.payload);
    },
    DELETE_NAME: (state, action) => {
      const newName = state.name.filter((name) => name !== "Kim");
      state.name = newName;
    },
  },
});

export const { ADD_NAME, DELETE_NAME } = manageSlicer.actions;
export default manageSlicer.reducer;

3) store.js에 새로 만든 리듀서를 추가한다.

  • 이 또한 마찬가지로 manageSlicer.reducer를 default로 export 했으므로 원하는 이름으로 import할 수 있다. 따라서 manageReducer로 import 했다.
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../reducer/counterSlice";
import manageReducer from "../reducer/manageSlicer";

const store = configureStore({
  reducer: {
    counter: counterReducer,
    // 새로만든 reducer 추가
    manager: manageReducer,
  },
});

export default store;

4) manage 폴더에 Manager.js 파일을 생성한다.

import { useSelector, useDispatch } from "react-redux";
import { ADD_NAME, DELETE_NAME } from "../reducer/manageSlicer";

const Manager = () => {
  const name = useSelector((state) => state.manager.name);
  const dispatch = useDispatch();
  return (
    <div>
      <br />
      <br />
      <br />
      {name.map((name) => {
        return <span key={name}>{name} </span>;
      })}
      <div>
        <button onClick={() => dispatch(DELETE_NAME("Kim"))}>Delete Kim</button>
        <button onClick={() => dispatch(ADD_NAME("John"))}>Add John</button>
      </div>
    </div>
  );
};

export default Manager;

5) App.js에 새로만든 Manager 컴포넌트를 추가한다.

App.js에 새로만든 Manager 컴포넌트를 추가한다.

최종 완성


RTK의 immer사용시의 규칙

  1. 기존에 존재하는 상태를 변경하려면 return value가 필요하지 않다.
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    // 기존 상태를 변화시키면 value를 return할 필요가 없다.
    todoAdded(state, action) {
      state.push(action.payload)
    },
  }
})
  1. 새로운 배열을 immutable하게 만들면 return 해야한다.
    • ex : filter() 사용
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    todoDeleted(state, action.payload) {
      // 새 배열을 immutable하게 만들면 return 해야함
      return state.filter(todo => todo.id !== action.payload)
    }
  }
})
  1. immutable update를 사용해 작업의 특정 부분을 수행하고 mutation을 통해 결과를 저장할 수도 있다.
    • 예를 들어 중첩 배열(nested array)를 filter 할 때 사용한다.
const todosSlice = createSlice({
  name: 'todos',
  initialState: {todos: [], status: 'idle'}
  reducers: {
    todoDeleted(state, action.payload) {
      // 새 배열을 immutable하게 만들고
      const newTodos = state.todos.filter(todo => todo.id !== action.payload)
      // mutate를 통해 기존 상태에 새 배열을 저장한다.
      state.todos = newTodos
    }
  }
})
  1. 화살표 함수에서 묵시적 반환(implicit return)을 하게 되면 이 rule이 깨지며 에러를 발생시킨다.

묵시적 반환 : 화살표 함수에서 중괄호(curly braces)를 생략하고 reutrn을 생략시킨 후 반환시키는 방법

따라서 화살표 함수에서 해당 rule을 지킬 수 있는 두 가지 방법이 있다.

1) 묵시적 반환시에 앞에 void 키워드를 넣는법

fixedReducer1: (state, action) => void state.push(action.payload),

2) 중괄호(curly braces)를 사용해 함수의 body를 만들기 -> 이 때 return은 하지 않아도 된다.

    fixedReducer2: (state, action) => {
      state.push(action.payload)
    },
profile
Front-End Developer

0개의 댓글