๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

react

React / ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ (2) - Redux, Redux Toolkit

Redux

์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, flux ๊ทœ๊ฒฉ์— reducer๊ฐ€ ๊ฒฐํ•ฉ๋œ ํŒจํ„ด์ด๋‹ค. Flux๋ณด๋‹ค ๋”์šฑ ๊ฐ„๋‹จํ•˜๋‹ค๋Š” ์ ์ด ํŠน์ง•์ด๋‹ค.

 

* Flux ํŒจํ„ด

Facebook์—์„œ ๋งŒ๋“  ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ์กฐ

๋ฐ์ดํ„ฐ ํ๋ฆ„ : View → Action  Dispatcher  Store  View ์—…๋ฐ์ดํŠธ

 

 

 

Redux์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„

๊ตฌ์„ฑ์š”์†Œ

  • Action : ์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”์‹œํ‚ฌ ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ๋‹ด์€ ๊ฐ์ฒด
  • Dispatch : ์•ก์…˜ ๊ฐ์ฒด๋ฅผ Reducer๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ์—ญํ• ์„ ํ•˜๋Š” ํ•จ์ˆ˜
  • Reducer : ๋ฆฌํ„ดํ•˜๋Š” ๊ฐ’์ด ์ƒˆ๋กœ์šด ์ƒํƒœ๊ฐ€ ๋˜๋Š” ํ•จ์ˆ˜
    ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด combineReducers ํ•จ์ˆ˜๋กœ ๋ฌถ์–ด ํ•˜๋‚˜๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
  • Store : Redux์˜ ์ „์—ญ ์ƒํƒœ ์ €์žฅ์†Œ, createStore ํ•จ์ˆ˜์— Reducer๋ฅผ ์ „๋‹ฌํ•ด ์ƒ์„ฑ

 

๋งŒ๋“ค์–ด์ง„ ๊ตฌ์„ฑ์š”์†Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค.

  1. App๊ณผ ์ „์—ญ ์ƒํƒœ ์ €์žฅ์†Œ ์—ฐ๊ฒฐ (์ฃผ๋กœ Main.jsx์—์„œ)
    <Provider store={store}>
  2. useSelector()๋กœ ์ƒํƒœ ์ €์žฅ์†Œ์—์„œ ์ƒํƒœ ๊บผ๋‚ด์„œ ์‚ฌ์šฉ
  3. useDispatch()๋กœ dispatch ํ•จ์ˆ˜ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ

 

+ Flux์™€ Redux์˜ ์ฐจ์ด

 

  Flux Redux
Dispatcher ๋ฐ˜๋“œ์‹œ ํ•„์š” Reducer๋กœ ๋Œ€์ฒด
Store ์—ฌ๋Ÿฌ ๊ฐœ ์กด์žฌ ๊ฐ€๋Šฅ 1๊ฐœ
State ๋ณ€๊ฒฝ Store ์•ˆ์—์„œ ์ง์ ‘ ๋ณ€๊ฒฝ ๋ถˆ๋ณ€ ์ƒํƒœ, Reducer๊ฐ€ ์ƒˆ ์ƒํƒœ ๋ฐ˜ํ™˜

 

 

Redux-Thunk

์•ก์…˜์œผ๋กœ ๊ฐ์ฒด ๋ง๊ณ ๋„ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฏธ๋“ค์›จ์–ด

=> Redux์—์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค!

 

 


 

 

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

์„ค์น˜

npm i redux react-redux

 

Redux-thunk ์„ค์น˜

npm i redux-thunk

 

 


 

 

์˜ˆ์‹œ 1 - ๊ธฐ๋ณธ

App.jsx

import { useSelector, useDispatch } from 'react-redux';
import { combineReducers, legacy_createStore } from 'redux';
import './App.css';

// ์•ก์…˜ ๊ฐ์ฒด ์ •์˜
const increment = {
  type: 'increment',
};
const decrement = {
  type: 'decrement',
};

// Reducer ์ •์˜
// ์ดˆ๊ธฐ๊ฐ’, action์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์„ค์ •
const counterReducer = (state = 0, action) => {
  // action์˜ type์— ๋”ฐ๋ผ ์ƒํƒœ ๋ณ€๊ฒฝ
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      return state;
  }
};

// Store ์ •์˜, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด ์ƒํƒœ ๊ด€๋ฆฌ ๊ฐ์ฒด
// legacy_createStore์€ ์‹ค์Šต์šฉ์œผ๋กœ ์‚ฌ์šฉ... ์š”์ƒˆ๋Š” redux toolkit์„ ๊ถŒ์žฅ
export const store = legacy_createStore(counterReducer);

function App() {
  // useSelector๋กœ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊บผ๋‚ด์˜ด
  const counter = useSelector((state) => state);
  // useDispatch๋กœ ์•ก์…˜์„ store๋กœ ๋ณด๋ƒ„
  const dispatch = useDispatch();

  // ํด๋ฆญ ์‹œ dispatch()๋ฉ”์„œ๋“œ๋กœ ์•ก์…˜์„ ๋ณด๋‚ด ์ƒํƒœ ๋ณ€๊ฒฝ!
  return (
    <>
      <div>Counter : {counter}</div>
      <button onClick={() => dispatch(increment)}>+</button>
      <button onClick={() => dispatch(decrement)}>-</button>
    </>
  );
}

export default App;

 

Main.jsx

import { createRoot } from 'react-dom/client';
import './index.css';
import App, { store } from './App.jsx';
import { Provider } from 'react-redux';

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

 

 

 

์˜ˆ์‹œ 2  - Reducer๊ฐ€ ๋‘ ๊ฐœ ์ด์ƒ

์ง€๊ธˆ์€ reducer๊ฐ€ ํ•˜๋‚˜์ง€๋งŒ ๋‘ ๊ฐœ ์ด์ƒ์ผ ๊ฒฝ์šฐ combineReducers์„ ์‚ฌ์šฉํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

import { useSelector, useDispatch } from 'react-redux';
import { combineReducers, legacy_createStore } from 'redux';
import './App.css';

// ์•ก์…˜ ๊ฐ์ฒด
const increment1 = { type: 'increment1' };
const decrement1 = { type: 'decrement1' };

const increment2 = { type: 'increment2' };
const decrement2 = { type: 'decrement2' };

// Reducer1
const counterReducer1 = (state = 4, action) => {
  switch (action.type) {
    case 'increment1':
      return state + 1;
    case 'decrement1':
      return state - 1;
    default:
      return state;
  }
};

// Reducer2
const counterReducer2 = (state = 0, action) => {
  switch (action.type) {
    case 'increment2':
      return state + 2;
    case 'decrement2':
      return state - 2;
    default:
      return state;
  }
};

// combineReducers()๋กœ ์‚ฌ์šฉํ•  Reducer๋“ค์„ ํ•ฉ์นจ
const rootReducer = combineReducers({ counterReducer1, counterReducer2 });

// ์ƒ์„ฑํ•œ Store์— Reducer๋“ค์„ ํ•ฉ์นœ rootReducer๋ฅผ ๋„˜๊ฒจ์คŒ
export const store = legacy_createStore(rootReducer);

function App() {
  // ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ(Reducer๋ณ„๋กœ ๊ฐ€์ ธ์™€์•ผํ•จ)
  const counter1 = useSelector((state) => state.counterReducer1);
  const counter2 = useSelector((state) => state.counterReducer2);
  // dispatch ํ•จ์ˆ˜ ๊ฐ€์ ธ์˜ค๊ธฐ 
  const dispatch = useDispatch();

  return (
    <>
      <div>Counter 1 : {counter1}</div>
      <button onClick={() => dispatch(increment1)}>+</button>
      <button onClick={() => dispatch(decrement1)}>-</button>
      <hr />
      <div>Counter 2 : {counter2}</div>
      <button onClick={() => dispatch(increment2)}>+</button>
      <button onClick={() => dispatch(decrement2)}>-</button>
    </>
  );
}

export default App;

 

 

 

์˜ˆ์‹œ 3 - Redux-thunk ์‚ฌ์šฉ

  1. Thunk๋ฅผ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ ์šฉ => applyMiddleware(thunk)๋ฅผ ํ†ตํ•ด store์— ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  2. dispatch์— ํ•จ์ˆ˜ ์ „๋‹ฌ์ด ๊ฐ€๋Šฅํ•ด์ง
    ๋งŒ๋“ค์–ด์ง„ ํ•จ์ˆ˜๋Š” dispatch์™€ getState๋ฅผ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์•„๋ž˜ ์˜ˆ์‹œ์—์„  ์•ˆ์”€
    + getState๋Š” ๋ณดํ†ต ์กฐ๊ฑด์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๋ณด๊ณ  ์•ก์…˜์„ ๊ฒฐ์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ (ex. counter๊ฐ€ 10 ๋ฏธ๋งŒ์ผ ๋•Œ ์•ก์…˜์„ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์„ ๋•Œ) ์“ฐ์ž„
  3. ํ•จ์ˆ˜ ์•ˆ์—์„œ ๋น„๋™๊ธฐ ์ฝ”๋“œ ์‹คํ–‰
  4. ๋น„๋™๊ธฐ ์ž‘์—… ์™„๋ฃŒ ํ›„ ์‹ค์ œ ์•ก์…˜ ๊ฐ์ฒด๋ฅผ dispatch
import { applyMiddleware, combineReducers, legacy_createStore } from 'redux';
import { thunk } from 'redux-thunk';

// ์ค‘๋žต

// thunk๋ฅผ ๋ฏธ๋“ค์›จ์–ด๋กœ ์ ์šฉํ•˜๊ณ , applyMiddleware(thunk)๋กœ store์— ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ๋‹ค!
export const store = legacy_createStore(rootReducer, applyMiddleware(thunk));

function App() {
  const counter1 = useSelector((state) => state.counterReducer1);
  const counter2 = useSelector((state) => state.counterReducer2);
  
  // useDispatch๋กœ ์•ก์…˜ ์ „์†ก ํ•จ์ˆ˜ ์ƒ์„ฑ
  const dispatch = useDispatch();
  
  // 2์ดˆ ๋’ค ์ฆ๊ฐ€์‹œ์ผœ์ฃผ๋Š” ํ•จ์ˆ˜ 
  const imcrement2sec = () => {
    return (dispatch) => {
      // 2์ดˆ ๋’ค increment1์„ dispatchํ•œ๋‹ค
      setTimeout(() => {
        dispatch(increment1);
      }, 2000);
    };
  };

  // thunk ๋•๋ถ„์— dispatch๊ฐ€ ํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ!! 
  return (
    <>
      <div>Counter 1 : {counter1}</div>
      <button onClick={() => dispatch(imcrement2sec())}>+</button>
      <button onClick={() => dispatch(decrement1)}>-</button>
    </>
  );
}

 

 

 

 


 

 

 

Redux Toolkit

Redux๋ฅผ ๋” ํŽธํ•˜๊ฒŒ ์“ฐ๊ธฐ ์œ„ํ•œ ๊ณต์‹ ํˆดํ‚ท์œผ๋กœ, ์š”์ƒˆ๋Š” Redux ๊ณต์‹์—์„œ๋„ ์ด Toolkit์„ ์“ฐ๊ธฐ๋ฅผ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋‹ค.

 

Redux์™€์˜ ์‚ฌ์šฉ ๋ฐฉ์‹ ์ฐจ์ด

Redux Redux Toolkit
Action createSlice
Reducer
Store configureStore

 

๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ถ€๋ถ„

  • <Provider store={store}></Provider>
  • useSelector()
  • useDispatch()

 

createSlice()

์•ก์…˜ ํƒ€์ž…, ์•ก์…˜ ์ƒ์„ฑ์ž, Reducer๋ฅผ ํ•œ ๋ฒˆ์— ์ƒ์„ฑ

createSlice({name:'counter', initalState:0, reducers:{
  increment(state, action){state+=1},
  decrement(state, action){state-=1}
}})

 

 

configureStore()

Reducer๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ ์ „์—ญ ์ƒํƒœ ์ €์žฅ์†Œ๋ฅผ ์ƒ์„ฑ

const store = configureStore({
  reducer:{
    counter:counterSlice.reducer,
    // ์—ฌ๋Ÿฌ๊ฐœ์ผ ๊ฒฝ์šฐ
    any:anySlice.reducer,
    // ... ๋“ฑ๋“ฑ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ์ „๋‹ฌํ•ด์ค„ ์ˆ˜ ์žˆ์Œ 
  }
})

 

 

์•ก์…˜ ์ƒ์„ฑ์ž ์‚ฌ์šฉ

createSlice์—์„œ ์ •์˜ํ•œ reducers ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ

dispatch(counterSlice.actions.increment());

 

 

createAsyncThunk()

Redux toolkit์—์„œ ์ œ๊ณตํ•˜๋Š” Thunk ์ƒ์„ฑ ํ•จ์ˆ˜๋ผ ๋”ฐ๋กœ ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฉฐ Redux-Thunk์ฒ˜๋Ÿผ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•จ

- ๋น„๋™๊ธฐ ๋กœ์ง์„ store ์™ธ๋ถ€๋กœ ๋ถ„๋ฆฌ

- ์ธ์ž : (์•ก์…˜ ํƒ€์ž… ๋ฌธ์ž์—ด, ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜, ์ถ”๊ฐ€ ์˜ต์…˜)

 

 


 

 

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

์„ค์น˜

npm install @reduxjs/toolkit react-redux

 

 

์˜ˆ์‹œ 1 - ๊ธฐ๋ณธ

import { useDispatch, useSelector } from 'react-redux';
import './App.css';
import { configureStore, createSlice } from '@reduxjs/toolkit';

// ์ƒํƒœ + ์•ก์…˜ ์ƒ์„ฑ
// createSlice() ์‚ฌ์šฉ
const counterSlice = createSlice({
  name: 'counter', // slice์˜ ์ด๋ฆ„
  initialState: 4, // ์ดˆ๊ธฐ ์ƒํƒœ ๊ฐ’
  reducers: { // ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜๋“ค (reducer + action ์—ญํ• )
    increment(state, action) {
      return state + 1;
    },
    decrement(state, action) {
      return state - 1;
    },
  },
});

// ์Šคํ† ์–ด ์ƒ์„ฑ
// configureStore() ์‚ฌ์šฉ
export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});

function App() {
  // store์—์„œ slice์˜ ์ด๋ฆ„์ธ counter๋กœ ์ ‘๊ทผํ•ด์„œ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ด
  const counter = useSelector((state) => state.counter);
  // ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ํ•จ์ˆ˜
  const dispatch = useDispatch();
  return (
    <>
      <div>Counter : {counter}</div>
      <button
          // counterSlice.actions๋Š” ์•ก์…˜ ๊ฐ์ฒด ๋ชจ์Œ
          dispatch(counterSlice.actions.increment());
        }}>
        +
      </button>
      <button
        onClick={() => {
          dispatch(counterSlice.actions.decrement());
        }}>
        -
      </button>
    </>
  );
}

export default App;

 

 

 

์˜ˆ์‹œ 2 - Thunk ์‚ฌ์šฉํ•˜๊ธฐ

const IncrementThunk2sec = createAsyncThunk(
  'counter/slowIncrement', // ์•ก์…˜ ํƒ€์ž…
   // payloadcreator ํ•จ์ˆ˜๋กœ, ์—ฌ๊ธฐ์„œ ๋น„๋™๊ธฐ ์ž‘์—…์ด ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ
   // value : thunk๋ฅผ dispatch(IncrementThunk2sec(๊ฐ’))์ฒ˜๋Ÿผ ์‚ฌ์šฉ ์‹œ ๋„˜๊ธฐ๋Š” ๊ฐ’
  (value, { dispatch }) => {
    setTimeout(() => {
      dispatch(counterSlice.actions.increment());
    }, 2000);
  },
);

function App() {
  const counter = useSelector((state) => state.counter);
  const dispatch = useDispatch();
  return (
    <>
      <div>Counter : {counter}</div>
      <button
        onClick={() => {
          dispatch(IncrementThunk2sec());
        }}>
        +
      </button>
    </>
  );
}

 

 

 

 

 

 

 

์ด๋ฏธ์ง€ ์ถœ์ฒ˜
Redux ํŒจํ„ด : https://qiita.com/dondoko-susumu/items/5a5cb4250b46738c9e48#redux