본문 바로가기

온라인 강의(유데미, 인프런 등)/React 완벽 가이드(유데미)

[react & RTK] Redux Toolkit 개념 및 예시 간단하게 살펴보기

반응형

Redux Toolkit

Redux가 공식적으로 만든 라이브러리로, Redux를 쉽게 사용할 수 있게 도와준다.

# Redux Toolkit 설치 방법

// Redux + Plain JS template
npx create-react-app my-app --template redux

// Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

// 기존 프로젝트에서 시작하기
npm install @reduxjs/toolkit

 

configureStore (createStore → configureStore)

reducer를 전달할 때는 reducer 라는 property name으로 전달해야 한다. slice에서 만든 reduce 함수를 가져와서 store에 추가한다.

// slice 1개 일 때
const store = configureStore({reducer: counterSlice.reducer })

slice가 여러 개라면 reducer의 값을 object로 하여 여러 reducer를 병합하는 방식을 사용한다(combineReducers 사용 x).

//slice 여러 개 일 때
const store = configureStore({reducer: { counter: counterSlice.reducer , auth: authSlice.reducer} })

createSlice (reducer + action → createSlice)

createSlice를 통해 Action과 Reducer를 한 번에 정의할 수 있다. Redux Toolkit은 immer 라이브러리를 내장하고 있어 기존 상태를 자동으로 복제하기 때문에 실수로라도 기존 상태 값을 직접 조작할 수 없다. 따라서 기존의 Redux 사용 방식과 달리 상태 값을 직접 변경하는 방식으로 코드를 작성해 재정의할 수 있다.

slice를 생성하려면 slice를 식별하기 위한 name, 초기 상태 값 initialState, 상태 업데이트 방법을 정의하는 하나 이상의 reducer 함수가 필요하다.

const counterSlice = createSlice({name, initialState, reducers})

slice가 생성되면 생성된 Redux 액션 생성자와 전체 slice에 대한 reducer 기능을 export 한다.

counterSlice.actions : useDispatch 훅을 이용해 dispatch할 수 있다(컴포넌트에서 상태 변경 시 사용). counterSlice.reducer : useSelector 훅을 이용해 저장소에서 전역 상태를 읽을 수 있다(store에서 reducer 함수들 merge 시 사용).

 

[예시]

// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counter';
import authReducer from './auth';

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

export default store;


// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import App from './App';
import store from './store/index';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);


// store/counter.js
import { createSlice } from '@reduxjs/toolkit';

const initialCounterState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: 'counter',
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

export const counterActions = counterSlice.actions;

export default counterSlice.reducer;

// store.auth.js
import { createSlice } from '@reduxjs/toolkit';

const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'authentication',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

export const authActions = authSlice.actions;

export default authSlice.reducer;


// Counter.js
import { useSelector, useDispatch } from 'react-redux';
import { counterActions } from '../store/counter';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector(state => state.counter.counter);
  const show = useSelector(state => state.counter.showCounter);

  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };

  const increaseHandler = () => {
    dispatch(counterActions.increase(10));
  };

  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };

  const toggleCounterHandler = () => {
    dispatch(counterActions.toggleCounter());
  };

  return (
    <div>
      <h1>Redux Counter</h1>
      {show && <div>{counter}</div>}
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={increaseHandler}>Increment by 10</button>
        <button onClick={decrementHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </div>
  );
};

export default Counter;

// Auth.js
import { useDispatch } from 'react-redux';

import classes from './Auth.module.css';
import { authActions } from '../store/auth';

const Auth = () => {
  const dispatch = useDispatch();

  const loginHandler = (event) => {
    event.preventDefault();

    dispatch(authActions.login());
  };

  return (
    <main className={classes.auth}>
      <section>
        <form onSubmit={loginHandler}>
          <div className={classes.control}>
            <label htmlFor='email'>Email</label>
            <input type='email' id='email' />
          </div>
          <div className={classes.control}>
            <label htmlFor='password'>Password</label>
            <input type='password' id='password' />
          </div>
          <button>Login</button>
        </form>
      </section>
    </main>
  );
};

export default Auth;

// header.js
import { useSelector, useDispatch } from 'react-redux';

import classes from './Header.module.css';
import { authActions } from '../store/auth';

const Header = () => {
  const dispatch = useDispatch();
  const isAuth = useSelector((state) => state.auth.isAuthenticated);

  const logoutHandler = () => {
    dispatch(authActions.logout());
  };

  return (
    <header className={classes.header}>
      <h1>Redux Auth</h1>
      {isAuth && (
        <nav>
          <ul>
            <li>
              <a href='/'>My Products</a>
            </li>
            <li>
              <a href='/'>My Sales</a>
            </li>
            <li>
              <button onClick={logoutHandler}>Logout</button>
            </li>
          </ul>
        </nav>
      )}
    </header>
  );
};

export default Header;
반응형