[React] Redux Middleware

강은비·2022λ…„ 1μ›” 19일
0

React

λͺ©λ‘ 보기
24/36
post-custom-banner

react μŠ€ν„°λ””μ—μ„œ λ¦¬μ•‘νŠΈλ₯Ό λ‹€λ£¨λŠ” κΈ°μˆ μ΄λΌλŠ” 책을 μ„ μ •ν–ˆκ³  이 책을 읽고 배운 것을 λ°”νƒ•μœΌλ‘œ μž‘μ„±λ˜μ—ˆλ‹€.


πŸ“Œ λ―Έλ“€μ›¨μ–΄λž€?

πŸ’‘ μ•‘μ…˜κ³Ό λ¦¬λ“€μ„œ μ‚¬μ΄μ˜ μ€‘κ°„μž

  • λ¦¬λ•μŠ€ λ―Έλ“€μ›¨μ–΄λŠ” μ•‘μ…˜μ΄ dispatchλ˜μ—ˆμ„ λ•Œ λ¦¬λ“€μ„œμ—μ„œ 이λ₯Ό μ²˜λ¦¬ν•˜κΈ°μ— μ•žμ„œ μ§€μ •λœ μž‘μ—…λ“€μ„ μ‹€ν–‰ν•œλ‹€.

✨ 미듀웨어 κΈ°λ³Έ ꡬ쑰

const loggerMiddleware = store => next => action => {

};

μ°Έκ³ : ν΄λ‘œμ €(μ»€λ§ν•¨μˆ˜)

  • store: λ¦¬λ•μŠ€ μŠ€ν† μ–΄ μΈμŠ€ν„΄μŠ€
  • action: λ””μŠ€νŒ¨μΉ˜λœ μ•‘μ…˜
  • next: ν•¨μˆ˜ ν˜•νƒœλ‘œ dispatch와 λΉ„μŠ·ν•œ κΈ°λŠ₯을 ν•œλ‹€.
    • next(action)을 ν˜ΈμΆœν•˜λ©΄ μ•‘μ…˜μ„ λ‹€μŒ λ―Έλ“€μ›¨μ–΄μ—κ²Œ μ „λ‹¬ν•˜κ³ , μ—†λ‹€λ©΄ λ¦¬λ“€μ„œμ—κ²Œ μ „λ‹¬ν•œλ‹€.
    • 반면, 미듀웨어 λ‚΄λΆ€μ—μ„œ store.dispatchλ₯Ό ν˜ΈμΆœν•˜λ©΄ 첫 번째 미듀웨어뢀터 λ‹€μ‹œ μ²˜λ¦¬ν•œλ‹€.
    • λ§Œμ•½ λ―Έλ“€μ›¨μ–΄μ—μ„œ nextλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©΄ μ•‘μ…˜μ€ λ¦¬λ“€μ„œμ—κ²Œ μ „λ‹¬λ˜μ§€ μ•ŠλŠ”λ‹€. 즉, μ•‘μ…˜μ΄ λ¬΄μ‹œλœλ‹€.

✨ redux-logger μ‚¬μš©ν•˜κΈ°

// src/index.js
import { createStore, applyMiddleware } from "redux";
import { createLogger} from "redux-logger";
import rootReducer from "./modules";

const logger = createLogger();
const store = createStore(rootReducer, applyMiddleward(logger));

πŸ“Œ 비동기 μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” 미듀웨어

✨ redux-thunk

  • 객체가 μ•„λ‹Œ ν•¨μˆ˜ ν˜•νƒœμ˜ μ•‘μ…˜μ„ λ””μŠ€νŒ¨μΉ˜ν•  수 있게 ν•΄μ€€λ‹€.
  • Thunk: νŠΉμ • μž‘μ—…μ„ λ‚˜μ€‘μ— ν•  수 μžˆλ„λ‘ 미루기 μœ„ν•΄ ν•¨μˆ˜ ν˜•νƒœλ‘œ 감싼 것을 μ˜λ―Έν•œλ‹€.
const addOne = x => x + 1;
const addOneThunk = x => () => addOne(x);
const fn = addOneThunk(1);
setTimeout(() =>{
    const value = fn();
    console.log(value);
}, 1000);
  • redux-thunk라이브러리λ₯Ό μ‚¬μš©ν•˜λ©΄ ν•¨μˆ˜ ν˜•νƒœμ˜ μ•‘μ…˜μ„ λ””μŠ€νŒ¨μΉ˜ν•  수 μžˆλ‹€. 그러면 λ¦¬λ•μŠ€ 미듀웨어가 κ·Έ ν•¨μˆ˜λ₯Ό 전달받아 store의 dispatch와 getStateλ₯Ό 인자둜 λ„£μ–΄ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œλ‹€.
const sampleThunk = () => (dispatch, getState) => {
    // ν˜„μž¬ μƒνƒœ 쑰회
    // μƒˆλ‘œμš΄ μ•‘μ…˜ λ””μŠ€νŒ¨μΉ˜
};
// src/index.js
import { createStore, applyMiddleware } from "redux";
import ReduxThunk from "redux-thunk";
import rootReducer from "./modules";

const store = createStore(rootReducer, applyMiddleward(ReduxThunk));

✨ redux-saga

κΈ°λ³Έ κ°œλ… μ€‘μ‹¬μœΌλ‘œ μž‘μ„±λ˜μ—ˆλ‹€.

❗ Side Effect 관리

  • redux-sagaλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ side effect듀을 μ‰½κ²Œ κ΄€λ¦¬ν•˜κ³  효과적으둜 μ‹€ν–‰ν•˜κ³  κ°„λ‹¨ν•œ ν…ŒμŠ€νŠΈμ™€ μ‰¬μš΄ μ—λŸ¬ 처리λ₯Ό λͺ©μ μœΌλ‘œ ν•œλ‹€.
  • μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ ν•„μš”ν•œ side effectλ₯Ό λ³„λ„μ˜ μŠ€λ ˆλ“œλ‘œ λΆ„λ¦¬ν•΄μ„œ 관리할 수 μžˆλ‹€.
  • side effect: 데이터 μš”μ²­ λ“±μ˜ 비동기 μž‘μ—…, λΈŒλΌμš°μ € μΊμ‹œ λ“±κ³Ό 같이 μˆœμˆ˜ν•˜μ§€ μ•Šμ€ 것듀
    • μžλ°”μŠ€ν¬λ¦½νŠΈ 관점: μ½”λ“œκ°€ μ™ΈλΆ€ 세계에 영ν–₯을 μ£Όκ±°λ‚˜ λ°›λŠ” 것
    • ν•¨μˆ˜ 관점: ν•¨μˆ˜κ°€ μΌκ΄€λœ κ²°κ³Όλ₯Ό 보μž₯ν•˜μ§€ λͺ»ν•˜κ±°λ‚˜, ν•¨μˆ˜ μ™ΈλΆ€ 어디든지 μ‘°κΈˆμ΄λΌλ„ 영ν–₯을 μ£ΌλŠ” 것
  • μ•‘μ…˜ μƒμ„±μžμ™€ λ¦¬λ“€μ„œ ν•¨μˆ˜μ˜ μˆœμˆ˜μ„±μ„ μœ μ§€ν•˜λ©° redux-sagaλ₯Ό μ΄μš©ν•˜μ—¬ μ‚¬μ΄νŠΈ μ΄νŽ™νŠΈλ₯Ό 관리할 수 μžˆλ‹€.

redux-saga 라이브러리λ₯Ό μ‚¬μš©ν•˜λŠ” 경우

  • κΈ°μ‘΄ μš”μ²­μ„ μ·¨μ†Œ μ²˜λ¦¬ν•΄μ•Ό ν•  λ•Œ (λΆˆν•„μš”ν•œ 쀑볡 μš”μ²­ 방지)
  • νŠΉμ • μ•‘μ…˜μ΄ λ°œμƒν–ˆμ„ λ•Œ λ‹€λ₯Έ μ•‘μ…˜μ„ λ°œμƒμ‹œν‚€κ±°λ‚˜ API μš”μ²­ λ“± λ¦¬λ•μŠ€μ™€ κ΄€κ³„μ—†λŠ” μ½”λ“œλ₯Ό μ‹€ν–‰ν•  λ•Œ
  • μ›Ήμ†ŒμΌ“μ„ μ‚¬μš©ν•  λ•Œ
  • API μš”μ²­ μ‹€νŒ¨ μ‹œ μž¬μš”μ²­ν•΄μ•Ό ν•  λ•Œ

전체적인 흐름

import { put, takeEvery } from "redux-saga/effect";


function* helloSaga(){
    console.log("hello saga");
}


// worker Saga: will perform the async increment task
function* incrementAsync(){
    yield delay(1000);
    yield put({ type: "INCREMENT" });
}

// watcher Saga: spawn a new incrementAsync task on each INCREMENT_ASYNC action
function* watchIncrementAsync(){
    yield takeEvery("INCREMENT_ASYNC", incrementAsync);
}

// root Saga
export default function* rootSaga() {
    yield all([
        helloSaga(),
        watchIncrementAsync()
    ]);
}
  • SagaλŠ” 객체λ₯Ό yieldν•˜λŠ” μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ΄λ‹€.
  • λ°˜ν™˜λœ 객체(yielded object)λŠ” λ―Έλ“€μ›¨μ–΄μ—κ²Œ μ „λ‹¬ν•˜λŠ” μΌμ’…μ˜ λͺ…령이닀.
  • λ―Έλ“€μ›¨μ–΄λŠ” Sagaκ°€ λ°˜ν™˜ν•œ 객체 (yielded object)λ₯Ό λ°›κ³  λͺ…령을 ν•΄μ„ν•˜μ—¬ νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•œλ‹€. κ·Έ λ™μ•ˆ SagaλŠ” 싀행이 μ€‘μ§€λœ μƒνƒœμ΄λ‹€.
  • λͺ¨λ“  Sagaλ₯Ό λ³‘λ ¬μ μœΌλ‘œ μ‹€ν–‰ν•  수 μžˆλ„λ‘ root Sagaλ₯Ό λ§Œλ“ λ‹€.
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware);
sagaMiddleware.run(rootSaga);

❗ Generator

  • redux-sagaλŠ” μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜ 문법을 기반으둜 side effect을 κ΄€λ¦¬ν•œλ‹€.
  • μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜ (function*)λ₯Ό ν˜ΈμΆœν•˜λ©΄ μ œλ„ˆλ ˆμ΄ν„°κ°€ λ°˜ν™˜λœλ‹€.
  • μ œλ„ˆλ ˆμ΄ν„°λŠ” next()λ©”μ„œλ“œλ₯Ό κ°–λŠ”λ° 이 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜λŠ” yield문을 λ§Œλ‚˜κΈ° μ „κΉŒμ§€ μ‹€ν–‰λœλ‹€.
  • 이후 λ‹€μ‹œ next()λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ 싀행이 μ€‘μ§€λœ μ‹œμ λΆ€ν„° λ‹€μ‹œ μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜κ°€ μ‹€ν–‰λœλ‹€.
    (μžμ„Έν•œ λ‚΄μš©μ€ Generator μ°Έκ³ )

    βœ” μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜λŠ” Callee, 이λ₯Ό ν˜ΈμΆœν•˜λŠ” ν•¨μˆ˜λŠ” Caller이닀.
    βœ” Caller Calleeκ°€ λ°˜ν™˜ν•œ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό 가지고 λ‘œμ§μ„ μˆ˜ν–‰ν•œλ‹€.
    βœ” CallerλŠ” Callee의 yield μ§€μ μ—μ„œ λ‹€μŒ 진행 μ—¬λΆ€/μ‹œμ μ„ μ œμ–΄ν•œλ‹€.
    βœ” CallerλŠ” Calleeλ₯Ό ν˜ΈμΆœν•˜λŠ” μ±…μž„λΏ μ•„λ‹ˆλΌ Callee λ‚΄λΆ€ 둜직 μˆ˜ν–‰μ— λŒ€ν•œ μ œμ–΄κΆŒμ„ κ°–λŠ”λ‹€.

  • redux-saga μž…μž₯μ—μ„œ 보면 미듀웨어가 Caller이고, μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μΈ Sagaκ°€ Calleeλ‹€.
  • λ―Έλ“€μ›¨μ–΄λŠ” Sagaλ₯Ό λŠμž„μ—†μ΄ λ™μž‘μ‹œν‚¨λ‹€.
  • Sagaκ°€ λ™μž‘ν•˜κΈ° μ‹œμž‘ν•˜λ©΄ μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ΄κΈ° λ•Œλ¬Έμ— yield문을 λ§Œλ‚˜λ©΄ 싀행이 μ€‘μ§€λ˜κ³  객체λ₯Ό yieldν•œλ‹€.
  • λ―Έλ“€μ›¨μ–΄λŠ” 이 객체λ₯Ό λ°›μ•„ ν•΄μ„ν•˜κ³  νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•œλ‹€.

❗ Effect

πŸ’‘ 미듀웨어에 μ˜ν•΄ μˆ˜ν–‰λ  λͺ…령을 λ‹΄κ³  μžˆλŠ” μˆœμˆ˜ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈ 객체

// μ•žμ„œ 봀던 μ½”λ“œ
function* incrementAsync(){
    yield delay(1000);
    yield put({ type: "INCREMENT" });
}
  • μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ delay(1000)λ₯Ό μ‹€ν–‰ν•˜μ—¬ Promise객체λ₯Ό λ°˜ν™˜ν•œλ‹€. 즉, ν•¨μˆ˜ 내뢀에 비동적인 둜직이 μ‘΄μž¬ν•œλ‹€.
// μˆ˜μ •λœ μ½”λ“œ
function* incrementAsync(){
    yield call(delay, 1000); // {CALL : {fn: delay, args: [1000]}}
    yield put({ type: "INCREMENT" }); // {PUT: {type: "INCREMENT"}}
}
  • callμ΄λ‚˜ put을 Effect Creators(μ΄νŽ™νŠΈ μƒμ„±μž)라고 ν•˜κ³ , λͺ…령을 λ‹΄κ³  μžˆλŠ” μˆœμˆ˜ν•œ 객체 (Effect)λ₯Ό yieldν•œλ‹€.
  • λ―Έλ“€μ›¨μ–΄λŠ” 이 객체λ₯Ό λ°›μ•„ λͺ…령듀을 해석해 μ²˜λ¦¬ν•˜κ³ , κ·Έ κ²°κ³Όλ₯Ό λ‹€μ‹œ Sagaμ—κ²Œ μ „λ‹¬ν•œλ‹€.
  • λ¬Όλ‘  SagaλŠ” Effectλ§Œμ„ yieldν•΄μ•Ό ν•˜λŠ” 것은 μ•„λ‹ˆλ‹€. Promise 객체도 yieldν•  수 μžˆμ§€λ§Œ 비동기 λ‘œμ§μ„ Saga λ‚΄λΆ€μ—μ„œ 직접 μ²˜λ¦¬ν•˜λ©΄ ν…ŒμŠ€νŠΈκ°€ μ–΄λ €μ›Œμ§€λŠ” λ“± μ—¬λŸ¬ 어렀움이 λ°œμƒν•œλ‹€.
  • λ˜λ„λ‘ Effectλ§Œμ„ yieldν•˜λŠ” Sagaλ₯Ό λ§Œλ“€μ–΄μ•Ό ν•œλ‹€.

    (μ°Έκ³ : Effect Creators)

❗ Error Handling

  • Saga λ‚΄λΆ€μ—μ„œ errorλ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ try/catch문을 μ‚¬μš©ν•œλ‹€.

❗ Task

  • ν•˜λ‚˜μ˜ Sagaκ°€ μ‹€ν–‰λ˜λŠ” 것

❗ Saga Helpers

takeEvery(action, sagaFn)

  • action이 λ°œμƒν•  λ•Œλ§ˆλ‹€ Sagaλ₯Ό μ‹€ν–‰ν•œλ‹€.
  • μ—¬λŸ¬ 개의 Taskλ₯Ό λ™μ‹œμ— μ‹œμž‘ν•  수 μžˆλ‹€.

takeLast(action, sagaFn)

  • λ§ˆμ§€λ§‰μœΌλ‘œ λ°œμƒν•œ ν•˜λ‚˜μ˜ action에 λŒ€ν•΄μ„œλ§Œ Sagaλ₯Ό μ‹€ν–‰ν•œλ‹€.
  • μ‹€ν–‰μ€‘μ΄λ˜ Taskλ₯Ό μ·¨μ†Œν•˜κ³  μƒˆλ‘œμš΄ Taskλ₯Ό μ‹€ν–‰ν•œλ‹€.

μ°Έκ³ 

post-custom-banner

0개의 λŒ“κΈ€