๐Ÿ‘ฉ‍๐Ÿ’ป/React

[react] redux๋กœ ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ธฐ #์‹œ์ž‘ํ•˜๋ฉฐ 2

ํ•œ๋‚˜ 2021. 3. 9. 01:44

์‹œ์ž‘ํ•˜๋ฉฐ

1ํŽธ์—์„œ ๋‹ค๋ฃจ์—ˆ๋˜ ๊ธ€์—์„œ๋Š” redux-actions๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, redux์™€ react-redux ๋ชจ๋“ˆ๋กœ๋„ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์„๊นŒ ์‹ถ์—ˆ๋‹ค. ํŒจํ„ด ์—ญ์‹œ ์ข€ ๋” ์ง๊ด€์ ์œผ๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ฆ๊ฒจ๋ณด๋Š” ์œ ํŠœ๋ฒ„์˜ Redux ๊ฐ•์˜๋ฅผ ํ•œ ๋ฒˆ ๋“ค์–ด๋ดค๋‹ค.

๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์„ฑ

actions/reducers๋กœ ๋‚˜๋ˆ ์ง„ ๊ตฌ์„ฑ

store
โ””modules
โ”” index.js
โ”” test.js
โ””config.js
โ””index.js

๊ธฐ์กด์—๋Š” ์œ„ ๊ฐ™์€ ๊ตฌ์„ฑ์ด์—ˆ๋‹ค. ์ด๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ actions์™€ reducers๋กœ ๊ตฌ๋ถ„ํ–ˆ๋‹ค.

๊ธฐ๋ณธ ์„ค์ •

1 step reducer ์ƒ์„ฑ

reducers ํด๋” ๋‚ด์— counter์™€ ๋กœ๊ทธ์ธ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” isLoggedIn reducer๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ธฐ๋กœ ํ•œ๋‹ค.

// src/reducers/counter.js

const counterReducer = (state = 0, action) => {
    switch(action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

export default counterReducer;
// src/reducers/isLogged.js
const LoggedReducer = (state = false, action) => {
    switch(action.type) {
        case 'SIGN_IN':
            return !state; // or true
        default:
            return state;
    }
}

export default LoggedReducer;

์ธ์ž๋กœ ๋ฐ›๋Š” state์—๋Š” ์ดˆ๊ธฐ๊ฐ’์„ ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” reducer๊ฐ€ ์‹คํ–‰ํ•˜๊ณ ์ž ํ•˜๋Š” action์„ ๋ฐ›๋Š”๋‹ค. ๋‘˜๋‹ค switch ๋ฌธ์„ ์‚ฌ์šฉํ•ด action.type์ด ์–ด๋–ป๊ฒŒ ๋“ค์–ด์˜ค๋Š”์ง€์— ๋”ฐ๋ผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด ๋‹ค๋ฅด๋‹ค. default ๋ถ€๋ถ„์„ ์จ์ฃผ์ง€ ์•Š์œผ๋ฉด return ๊ฐ’์ด undefined์ผ ๋•Œ ์—๋Ÿฌ๋ฅผ ๋‚ผ ๊ฒƒ์ด๋ฏ€๋กœ ์ฃผ์˜.

์ด๋ ‡๊ฒŒ Reducer๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋  ํ…๋ฐ, ์ด๋Š” ํ•œ ๊ตฐ๋ฐ์— ๋ชจ์•„ (= combine) ๋‚ด๋ณด๋‚ด์ฃผ๋Š” ์—ญํ• ์„ ํ•  ํŒŒ์ผ์ด ํ•„์š”ํ•˜๋‹ค. reducers ํด๋” ๋‚ด index.js์— ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•œ๋‹ค.

step 2 combineReducer

import counterReducer from './counter';
import LoggedReducer from './isLogged';
import { combineReducers } from 'redux'

const rootReducer = combineReducers({
    counter : counterReducer, 
    isLogged : LoggedReducer
})


export default rootReducer;

redux ๋ชจ๋“ˆ์ด ์ œ๊ณตํ•˜๋Š” combineReducers ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๊ตณ์ด ๊ฐ ๋ฆฌ๋“€์„œ์— ์ด๋ฆ„์„ ๋ถ™์ด์ง€ ์•Š๋Š”๋‹ค ํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•ด๋„ ๋œ๋‹ค.

const rootReducer = combineReducers({
    counterReducer,  // same as counterReducer : counterReducer
    LoggedReducer // same as LoggedReducer : LoggedReducer

step 3 actions

reducer์˜ ์ธ์ž๊ฐ€ ๋˜๋Š” actions๋ฅผ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด actions ํด๋”์˜ index.js๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•œ๋‹ค.

export const increment = () => {
    return {
        type: 'INCREMENT'
    }
}

export const decrement = () => {
    return {
        type: 'DECREMENT'
    }
}

์—ฌ๊ธฐ์„œ type์€ ์ผ์ข…์˜ 'name'์œผ๋กœ, ๊ด€์Šต์ ์œผ๋กœ type์ด๋ผ ์ ์ง€๋งŒ name ๋“ฑ์œผ๋กœ ์ ์–ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.

step 4 index.js์—์„œ store๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ ์„ค์ •ํ•˜๊ธฐ

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore } from 'redux';
import allReducers from './reducers'; // webpack automatically detects whether its folder has index.js file. so this can be omitted
import { Provider } from 'react-redux'; // Connects our global states to our entire App. 

const store = createStore(allReducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

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

์œ„ ์ฃผ์„์—์„œ ์ ์–ด๋‘” ๊ฒƒ์ฒ˜๋Ÿผ ./reducer๋ผ๊ณ ๋งŒ ์ ์–ด๋„ ๊ธฐ๋ณธ index.js ํŒŒ์ผ์„ ์•Œ์•„์„œ ๊ฐ์ง€ํ•˜๊ณ  importํ•œ๋‹ค. ./reducers/index.js์™€ ๋™์ผํ•˜๋‹ค.
store๋ฅผ ๋งŒ๋“ค๊ณ  -> combinedํ•œ reducer๋ฅผ import ํ•˜๊ณ  -> ์ด๋ ‡๊ฒŒ ๊ฐ€์ ธ์˜จ global states๋ฅผ ์•ฑ๊ณผ ์—ฐ๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Provider๋ฅผ importํ•˜๋Š” ์ˆœ์ด๋‹ค.
์—ฌ๊ธฐ์„œ createStore ํ•จ์ˆ˜์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋“ค์–ด์˜ค๋Š” devtool ๊ด€๋ จ ์„ค๋ช…์€ 1ํŽธ์— ์—…๋ฐ์ดํŠธํ•ด๋‘์—ˆ๋‹ค.

step 5 ์ปดํฌ๋„ŒํŠธ์—์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions'; 


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

  return (
    <div className="App">
      <h1>Counter : {counter}</h1>
      <button onClick={() => {
        dispatch(increment())
      }}>+</button>
      <button onClick={() => {
        dispatch(decrement())
      }}>-</button>
      {isLogged ? <h1>valuable information I shouldn't see</h1> : ''}
    </div>
  );
}

export default App;

global state์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด react-redux๊ฐ€ ์ œ๊ณตํ•˜๋Š” useSelector๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
๋งˆ์ฐฌ๊ฐ€์ง€๋กœ dispatch๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด react-redux๊ฐ€ ์ œ๊ณตํ•˜๋Š” useDispatch๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐ actions๋„ ๋ถˆ๋Ÿฌ์™€์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด, ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์œ„์ฒ˜๋Ÿผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. dispatch()๋ฅผ ํ†ตํ•ด ์ „์ฒด redux์˜ ํ๋ฆ„์„ ์ผ์œผํ‚ค๊ณ , actions๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค. ๊ฐ ์ธ์ž๋Š” ๋ช…์‹œ๋œ ์ƒ์ˆ˜์˜ type์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์‹ค์ œ ํ•ด๋‹น reducer์˜ switch ๋ฌธ์„ ํ†ตํ•ด ๊ฐ์ง€๋œ case์— ๋”ฐ๋ผ state์— ๋ณ€ํ™”๋ฅผ ์ค€๋‹ค.

 

devTool

devTool์„ ์ฒ˜์Œ ์จ๋ดค๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ์ผ์–ด๋‚œ ๋ณ€ํ™”๋ฅผ ๋ˆˆ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.