리덕스 툴킷(Redux Toolkit)

sudyn·2023년 5월 4일

redux

목록 보기
2/4

리덕스 툴킷(Redux Toolkit) = RTK

기존의 리덕스 ducks패턴의 요소들이 전체적인 코드의 양을 늘린다는 불편함으로 인해 개량한 것

리덕스와 구조나 패러다임이 모두 똑같다. (dispatch,useSelector 사용)
모듈 부분만 차이가 있다.

일반 redux와 비교(counter)

일반 redux로 counter 코드

configStore.js

import { createStore, combineReducers } from 'redux';
import counter from '../modules/counter';

// 모든 리듀서를 합쳐(combinReducers) rootReducer를 만들고
const rootReducer = combineReducers({
  counter,
});

// store를 create한다.
const store = createStore(rootReducer);

// 이 스토어를 바깥으로 내보내서(app)
export default store;

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './redux/config/configStore';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // 애플리케이션 내부로 provider api로 리듀서를 주입하면 전역 상태관리자로 사용가능
  <Provider store={store}>
    <App />
  </Provider>
);

counter.js

// 리듀서

//Action Value - 상수로 만든이유? 휴먼 에러 방지
const ADD_NUMBER = 'counter/ADD_NUMBER';
const MINUS_NUMBER = 'counter/MINUS_NUMBER';

// Action Creator
export const addNumber = (payload) => {
  return {
    type: ADD_NUMBER,
    payload,
  };
};

export const minusNumber = (payload) => {
  return {
    type: MINUS_NUMBER,
    payload,
  };
};

// 초기값
const initialState = {
  number: 0,
};

// Reducer: 변화를 일으키는 함수
const counter = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMBER:
      return {
        number: state.number + action.payload,
      };
    case MINUS_NUMBER:
      return {
        number: state.number - action.payload,
      };
    default:
      return state;
  }
};

export default counter;

App.jsx

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addNumber, minusNumber } from './redux/modules/counter';

function App() {
  const number = useSelector((state) => state.counter.number);
  const dispatch = useDispatch();

  // dispatch할때 type,payload를 직접 입력하는 것이 아니라, 휴먼에러를 줄이기위해
  // action creator를 입력해준다.
  const onPlusButtonClickHandler = () => {
    dispatch(addNumber(1));
  };

  const onMinusButtonClickHandler = () => {
    dispatch(minusNumber(1));
  };

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
    </div>
  );
}

export default App;

리덕스 툴킷 활용한 counter

  1. redux toolkit 패키지 설치

    yarn add @reduxjs/toolkit

configStore.js

// import { createStore, combineReducers } from 'redux';
import counter from '../modules/counter';
import { configureStore } from '@reduxjs/toolkit';

// AS-IS : 일반 reducer
// const rootReducer = combineReducers({
//   counter,
// });

// const store = createStore(rootReducer);

// TO-BE : redux toolkit
// 모듈(slice)가 여러개인 경우 추가할때마다 reducer안에 각 모듈의 slice.reducer를 추가해줘야한다.
const store = configureStore({
  reducer: {
    counter: counter,
    // todos: todos,
  },
});

export default store;

counter.js

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

// AS-IS : 일반 redux

//Action Value - 상수로 만든이유? 휴먼 에러 방지
// const ADD_NUMBER = 'counter/ADD_NUMBER';
// const MINUS_NUMBER = 'counter/MINUS_NUMBER';

// Action Creator
// export const addNumber = (payload) => {
//   return {
//     type: ADD_NUMBER,
//     payload,
//   };
// };

// export const minusNumber = (payload) => {
//   return {
//     type: MINUS_NUMBER,
//     payload,
//   };
// };

// Reducer: 변화를 일으키는 함수
// const counter = (state = initialState, action) => {
//   switch (action.type) {
//     case ADD_NUMBER:
//       return {
//         number: state.number + action.payload,
//       };
//     case MINUS_NUMBER:
//       return {
//         number: state.number - action.payload,
//       };
//     default:
//       return state;
//   }
// };

// export default counter;



// TO-BE: redux toolkit
// 초기값
const initialState = {
  number: 0,
};

//createSlice() API : Action Value, Action Creator, Reducer가 하나로 합쳐졌다
const counterSlice = createSlice({
  name: 'counter', // 모듈 이름
  initialState, // 모듈의 초기 상태값
  reducers: { // 모듈의 reducer 로직
    // 리듀서 안에서 만든 함수 자체가 리듀서의 로직이자, 액션크리에이터가 된다.
    // 그리고 action value까지 함수의 이름을 따서 자동으로 만들어진다.
    addNumber: (state, action) => {
      state.number = state.number + action.payload;
    },
    minusNumber: (state, action) => {
      state.number = state.number - action.payload;
    },
  },
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const {addNumber, minusNumber} = counterSlice.actions;
// reducer는 configStore에 등록하기 위해 export default한다.
export default counterSlice.reducer;

일반 redux와 비교(todo)

우리는 modules에서 액션아이템을 만들어서 액션크리에이터를 내보냈다. 그 액션크리에이터로 컴포넌트에서액션객체(,payload dispatch애소 사용했었다.

기존 moduel에서 관리했는데 이렇게 3개를
1. action creator
2. 리듀서 export
3. action value

너무 코드가 길고 많아서 createSlice API를 제공해서 위에서 사용하는 3가지를 한번에 처리할 수 있다.

configStore.js, todos.js만 수정해주면 된다.

immer

immer를 사용하면 상태를 업데이트 할 때, 불변성을 신경쓰지 않으면서 업데이트를 해도 immer가 불변성 관리를 대신 해준다!

따라서 이전에는 원본 배열을 훼손하는 push 등의 사용이 권장되지 않았지만 RTK에서는 사용이 가능하다.

import { createSlice } from '@reduxjs/toolkit';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';

// action items
// const ADD_TODO = "ADD_TODO";
// const REMOVE_TODO = "REMOVE_TODO";
// const SWITCH_TODO = "SWITCH_TODO";

/**
 * 메서드 개요 : todo 객체를 입력받아, 기존 todolist에 더함
 * 2022.12.16 : 최초작성
 *
 * @param {todo 객체} payload
 * @returns
 */
// export const addTodo = (payload) => {
//   return {
//     type: ADD_TODO,
//     payload,
//   };
// };

/**
 * 메서드 개요 : todo의 id를 입력받아, 일치하는 todolist를 삭제
 * 2022.12.16 : 최초작성
 *
 * @param {todo의 id} payload
 * @returns
 */
// export const removeTodo = (payload) => {
//   return {
//     type: REMOVE_TODO,
//     payload,
//   };
// };

/**
 * 메서드 개요 : todo의 id를 입력받아, 일치하는 todo 아이템의 isDone을 반대로 변경
 * 2022.12.16 : 최초작성
 *
 * @param {*} payload
 * @returns
 */
// export const switchTodo = (payload) => {
//   return {
//     type: SWITCH_TODO,
//     payload,
//   };
// };

// initial states
const initialState = [
  {
    id: uuidv4(),
    title: '리액트 공부하기',
    contents: '빨리빨리 암기하기',
    isDone: false,
  },
  {
    id: uuidv4(),
    title: '스프링 공부하기',
    contents: '인강 열심히 들어보기!!',
    isDone: true,
  },
  {
    id: uuidv4(),
    title: '데이트',
    contents: '홍대입구역에서 3시까지',
    isDone: false,
  },
];

// reducers
// const todos = (state = initialState, action) => {
//   switch (action.type) {
//     case ADD_TODO: // 기존의 배열에 입력받은 객체를 더함
//       return [...state, action.payload];
//     case REMOVE_TODO: // 기존의 배열에서 입력받은 id의 객체를 제거(filter)
//       return state.filter((item) => item.id !== action.payload);
//     case SWITCH_TODO: // 기존의 배열에서 입력받은 id에 해당하는 것만 isDone을 반대로 변경(아니면 그대로 반환)
//       return state.map((item) => {
//         if (item.id === action.payload) {
//           return { ...item, isDone: !item.isDone };
//         } else {
//           return item;
//         }
//       });
//     default:
//       return state;
//   }
// };

// export
// export default todos;

const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: (state, action) => {
      // 일반 redux에서는 push로 원본을 복사하지 않고 상태를 변경하는 것이 불가했는데
      // RTK에서는 immer를 제공해주어서 상태를 업데이트할 때 불변성을 고려하지 않아도 대신 관리해주므로 사용가능해졌다.
      // state.push(action.payload);
      return [...state, action.payload];
    },
    removeTodo: (state, action) => {
      return state.filter((item) => item.id !== action.payload);
    },
    switchTodo: (state, action) => {
      return state.map((item) => {
        if (item.id === action.payload) {
          return { ...item, isDone: !item.isDone };
        } else {
          return item;
        }
      });
    },
  },
});

export const { addTodo, remoteTodo, switchTodo } = todosSlice.actions;
export default todosSlice.reducer;
// import { createStore } from "redux";
// import { combineReducers } from "redux";
import todos from "../modules/todos";
import { configureStore } from "@reduxjs/toolkit";

// AS-IS: 일반 redux
// 1. create rootReducer with reducers
// const rootReducer = combineReducers({
//   todos,
// });

// // 2. create store
// const store = createStore(rootReducer);

// TO-BE : redux toolkit
const store = configureStore( {
  reducer: {
    todo: todos,
  }
});

// 3. export
export default store;
profile
개발계발하는 프론트엔드 개발자🍠

0개의 댓글