Redux-Toolkit 연습하기

지현·2023년 7월 11일
0

React

목록 보기
10/11
post-thumbnail

리덕스 툴킷 간단 설명

리덕스 툴킷은 상태관리 라이브러리인 리덕스의 문제점을 보완하기 위해 만들어졌다.

  • store에서 state를 구독하여 어떻게 변경시킬지에 대한 정보를 담아서 action이라는 객체를 만든다.

  • 그리고 그 action을 dispatch(처리될 작업을 실행시키고 보내는 것)해서 reducer에 넘겨준다

  • reducer는 action의 정보를 보고 state를 정해진 규칙에 따라 변경한다.

  • 최종적으로는 전역state가 변경되고 해당하는 state를 구독하던 component들은 리렌더링된다.

하지만 이러한 리덕스에는 여러 불편한 점이 있었다.

  1. 기존 redux는 리덕스 스토어를 구성하는 것은 너무 복잡했다.
  2. 너무 많은 패키지를 설치해야했다. (redux devtool, immer, thunk 등) redux-toolkit 내부에 이미 설치가 되어 있기에 굳이 설치 할 필요가 없다.
  3. 보일러 플레이트 코드가 너무 많다.

    보일러 플레이트 (Boiler Plate) : 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말한다. 간단히 말해서 '묻지도 따지지도 않고 써야하는 코드'

연습

리덕스 툴킷은 복잡한 리덕스를 보완하기 위해 고안되었지만 아직 나에게는 리덕스 툴킷도 연습이 필요하다. 때문에 간단히 반복되는 패턴의 방식으로 리덕스 툴킷 상태관리에 익숙해져보고자 한다.

구현하고자 하는 기능은 Counter, Todo, Auth, Cart이다.

반복되는 코드 설명

  • useDispatch() : 생성한 action을 useDispatch를 통해 발생시킬 수 있다, 만들어둔 액션 생성 함수를 import한다.
  • useSelector() : 리덕스의 state를 조회할 수 있다.
  • createSlice : 리듀서 함수의 객체, 슬라이스 이름, 초기 상태 값을 받아들이고 해당 액션 생성자와 액션 유형으로 슬라이스 리듀서를 자동으로 생성

store.js

import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './counterSlice';
import todoSlice from './todoSlice';
import authSlice from './authSlice';
import cartSlice from './cartSlice';

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    todo: todoSlice.reducer,
    auth: authSlice.reducer,
    cart: cartSlice.reducer,
  },
});

export default store;

이렇게 store.js에서 구독하고자 하는 state를 설정한다.

Counter

plus, minus, reset 기능

counterSlice.js

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

const initialState = {
  id: 1,
  value: 0,
};

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    plus: (state, action) => {
      state.value = state.value + action.payload;
    },
    minus: (state, action) => {
      state.value = state.value - action.payload;
    },
    init: (state, action) => {
      state.value = 0;
    },
  },
});

export default counterSlice;
export const { plus, minus, init } = counterSlice.actions;

CounterRedux.jsx

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { plus, minus, init } from '../store/counterSlice';

const CounterRedux = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => {
    return state.counter.value;
  });
  const plusNum = () => {
    dispatch(plus(1));
  };
  const minusNum = () => {
    dispatch(minus(1));
  };
  const resetNum = () => {
    dispatch(init());
  };

  return (
    <div>
      <hr />
      <div>{count}</div>
      <button onClick={minusNum}>-</button>
      <button onClick={plusNum}>+</button>
      <button onClick={resetNum}>reset</button>
    </div>
  );
};

export default CounterRedux;

Todo

add, delete, check 기능

todoSlice.js

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

const initialState = [];

export const todoSlice = createSlice({
  name: 'todolist',
  initialState,
  reducers: {
    addTodo: (state, action) => {
      return [...state, action.payload];
    },
    deleteTodo: (state, action) => {
      return state.filter((todo) => todo.id !== action.payload);
    },
    checkTodo: (state, action) => {
      const todo = state.find((todo) => todo.id === action.payload);
      if (todo) {
        todo.checked = !todo.checked;
      }
    },
  },
});

export default todoSlice;
export const { addTodo, deleteTodo, checkTodo } = todoSlice.actions;

TodoRedux.jsx

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addTodo, deleteTodo, checkTodo } from '../store/todoSlice';

const TodoRedux = () => {
  const [input, setInput] = useState('');

  const dispatch = useDispatch();
  const todos = useSelector((state) => {
    return state.todo;
  });
  const onCreate = () => {
    if (input.trim() !== '') {
      const newTodo = {
        id: Date.now(),
        text: input,
        checked: false,
      };
      dispatch(addTodo(newTodo));
    }
    setInput('');
  };
  const onDelete = (id) => {
    dispatch(deleteTodo(id));
  };
  const handleInput = (e) => {
    setInput(e.target.value);
  };
  const onChecked = (id) => {
    dispatch(checkTodo(id));
  };

  return (
    <div>
      <h2>Redux로 할 일 목록</h2>
      <input
        type="text"
        placeholder="오늘할일"
        onChange={handleInput}
        value={input}
      />
      <button onClick={onCreate}>추가하기</button>
      <ul>
        {todos.map((item) => (
          <div key={item.id}>
            <li
              onClick={() => onChecked(item.id)}
              style={
                item.checked
                  ? { textDecoration: 'line-through' }
                  : { textDecoration: 'none' }
              }
            >
              {item.text}
            </li>
            <button onClick={() => onDelete(item.id)}>삭제하기</button>
          </div>
        ))}
      </ul>
    </div>
  );
};

export default TodoRedux;

Auth

auth라기에는 그저 상태에 따라 토글하는 정도인...

authSlice.js

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

const initialState = {
  value: false,
};

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    changeAuth: (state, action) => {
      state.value = !action.payload;
    },
  },
});

export default authSlice;
export const { changeAuth } = authSlice.actions;

AuthRedux.jsx

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { login, logout, changeAuth } from '../store/authSlice';

const AuthRedux = () => {
  const dispatch = useDispatch();
  const auth = useSelector((state) => {
    return state.auth.value;
  });
  const loginUser = () => {
    dispatch(login(true));
  };
  const logoutUser = () => {
    dispatch(logout(false));
  };
  const authChange = () => {
    dispatch(changeAuth(auth));
  };
  return (
    <div>
      <h2>Redux로 auth상태에 따른 토글 만들기</h2>
      <p>{auth ? '로그인상태' : '로그아웃상태'}</p>
      <button onClick={authChange}>
        {auth ? '로그아웃하기' : '로그인하기'}
      </button>
    </div>
  );
};

export default AuthRedux;

Cart

상품1 과 상품2를 넣는 action 감지

cartSlice.js

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

const initialState = [];

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addItem_1: (state, action) => {
      return [...state, action.payload];
    },
    addItem_2: (state, action) => {
      return [...state, action.payload];
    },
    deleteItem: (state, action) => {
      return state.filter((item) => item.id !== action.payload);
    },
  },
});

export default cartSlice;
export const { addItem_1, addItem_2, deleteItem } = cartSlice.actions;

CartRedux.jsx

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addItem_1, addItem_2, deleteItem } from '../store/cartSlice';

const CartRedux = () => {
  const dispatch = useDispatch();
  const cart = useSelector((state) => {
    return state.cart;
  });
  const addItem01 = () => {
    const newItem = {
      id: Date.now(),
      value: '상품1',
      price: 10000,
    };
    dispatch(addItem_1(newItem));
  };
  const addItem02 = () => {
    const newItem = {
      id: Date.now(),
      value: '상품2',
      price: 20000,
    };
    dispatch(addItem_2(newItem));
  };

  const onDelete = (id) => {
    dispatch(deleteItem(id));
  };
  return (
    <div>
      <h2>Redux로 쇼핑카트 만들기</h2>

      <ul>
        {cart.map((item) => (
          <li key={item.id}>
            {item.value} - {item.price}<button onClick={() => onDelete(item.id)}>제거</button>
          </li>
        ))}
      </ul>
      <button onClick={addItem01}>상품1 추가</button>
      <button onClick={addItem02}>상품2 추가</button>
    </div>
  );
};

export default CartRedux;

정리

처음에는 사실 잘 이해가 되지 않았는데 다양한 예시로 상태를 변화시켜보니 어떤 로직으로 움직이는지 어느정도 이해가 되었다. 하지만 실제로 거대한 프로젝트에서 쓰기 위해서는 더욱 연습이 필요할듯싶다.

상태관리 라이브러리에는 리덕스(툴킷) 이외에도 다양하다. 각 라이브러리의 장단점을 비교하여 익히는 과정이 필요할 것 같다.

0개의 댓글