λΈλ‘κ·Έ λͺ©μ°¨
redux-saga
λ 리λμ€ μνκ³λ₯Ό μ§ν±νκ³ μλ λ€μν λ―Έλ€μ¨μ΄ μ€ ν«ν λ―Έλ€μ¨μ΄λ€. λ―Έλ€μ¨μ΄λ(middleware Express.jsλ‘ μλ²λ₯Ό ꡬμΆν λ μ¬μ©λλ μ¬λ¬ λ―Έλ€μ¨μ΄μ μ¬μ©μ±κ³Ό λλμ΄ κ±°μ λΉμ·νλ€. nextλΌλ λ©μλλ‘ λ€μ λμμ λΆλ₯΄λ κ²λ λμΌνλ€.(λ³΄ν΅ λ¦¬λμ€μμλ 리λμλ₯Ό νΈμΆνλ€.) μ΅μ’
μ μΈ λμ μ²λ¦¬ μ΄μ μ νΉμν λμμ λ§λ€μ΄λ΄λ μν μ νλ μ λ λΉμ·νλ€. 리λμ€μμ μ΅μ’
μ μΈ μ²λ¦¬λ μνκ°μ λν μ΄λ€ λ³νλ₯Ό μλ―Ένλ€. νΉμν λμμ λΉλκΈ° μ²λ¦¬λ±μ λ€ μ μλ€.
redux-thunk
λ λνμ μ΄κ³ κ°λ¨ν 리λμ€ λ―Έλ€μ¨μ΄λ€. κ°λ²Όμ΄ λΉλκΈ°μ²λ¦¬ λͺ©μ μΌλ‘ λ§μ΄ μ¬μ©λλ€. λͺ¨λ μμ²΄λ‘ λΆλ¬μμ μ¬μ©ν μλ μμ§λ§ λ―Έλ€μ¨μ΄ κ·Έ μμ²΄λ‘ μμ©νκΈ° μ’μ μ½λλ‘ κ΅¬μ±λμ΄μλ€. νμ§λ§ apiλ‘ μ¬λ¬ λΉλκΈ°μ λμμ μ²λ¦¬νλ κ²μλ μ‘°κΈ λ¬΄λ¦¬κ° μμ΄μ μ λλ μ΄ν° λ¬Έλ² κΈ°λ°μ redux-saga
κ° λ§μ΄ μ£Όλͺ©λ°κ³ μλ€. κ·Έλ¦¬κ³ μ€λ λ€λ€λ³Ό λ΄μ©λ 리λμ€ μ¬κ°λ€.
리λμ€ μ¬κ°λ μλ°μ€ν¬λ¦½νΈμ μ λλ μ΄ν° λ¬Έλ²μ μ΄μ©νλ€. function*
ννμ νΉμν ν¨μλ‘ μμ±λ μ λλ μ΄ν° κ°μ²΄λ { value, done }
μμ±μ κ°μ§κ³ μλ μ λ§ νΉμν κ°μ²΄λ€. μ λλ μ΄ν° ν¨μ λ΄λΆμμ yieldλΌκ³ νλ ν€μλλ‘ λ€μ κ°, λμμ μ μ΄νλ€. κ·Έλμ while(true)
μ κ°μ΄ νλ‘κ·Έλ¨μ ν°νΈλ¦¬λ 무ν루νλ μ λλ μ΄ν° ν¨μ λ΄λΆμμ μ¬μ©ν μ μλ€. yield ν€μλλ₯Ό λ§λλ©΄ λ€μ λ‘μ§μ΄λ κ°μ μ λ¬νκ³ μ°μ ν¨μμμ λ²μ΄λκΈ° λλ¬Έμ΄λ€. .next()
λΌκ³ νλ λ©μλλ₯Ό λ°μμ λλ§ λ€μ λμμ μ²λ¦¬νκΈ° λλ¬Έμ λΉλκΈ°μ μΈ μ²λ¦¬λ₯Ό μ μ΄νκΈ° μ’λ€.
리λμ€ μ¬κ°κ° μ λλ μ΄ν° λ¬Έλ² κΈ°λ°μΈ κ²λ λΉλκΈ°μ μ²λ¦¬μ μΈμκ³Ό μ μ΄λ₯Ό μ ν΅μ νκΈ° μν¨μ΄λ€. 리λμμ μ μλ νΉμ ν μ‘μ μ κΈ°λ€λ¦¬λ€κ° μ‘μ μ΄ λ°μνλ μμ μμ yieldμ λ±λ‘λ ν¨μλ λ‘μ§μ΄ λμνκ² νλ κ²μ΄λ€. μ΄λ° μ²λ¦¬λ€μ 리λμ€ μ¬κ°μ 미리 μ μλ μ¬λ¬ λΆμν¨κ³Ό(effects) ν¨μλ€λ‘ λμνκ² λλ€.
리λμ€μμ λμνλ κ²μ΄κΈ° λλ¬Έμ κΈ°λ³Έμ μΌλ‘ μ‘μ , (μ‘μ μμ±μ), 리λμ, μ€ν μ΄λ ꡬμ±μ΄ λμ΄μμ΄μΌ νλ€. κ²°κ΅ λ¦¬λμ€λ₯Ό μ¬μ©νλ μ΄μ λ βμνκ° λ³νβμ΄κΈ° λλ¬Έμ΄λ€. κ°λ¨νκ² μ μ μ λ‘κ·ΈμΈμ μΈμ§νλ 리λμ€ λ‘μ§μ ꡬμ±ν΄λ³΄λ©΄μ μ¬κ°μ μ¬μ©λ²μ νμΈν΄λ³΄μ.
// reducer/user.js
// action
const LOGIN_REQ = 'LOGIN_REQ';
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAIL = 'LOGIN_FAIL';
const loginRequestAction = (userData) => ({
type: 'LOGIN_REQ',
data: userData
});
// initialState
const INITIAL_STATE = {
loginPendding: false,
loginDone: false,
loginError: null,
userData: null
};
// reducer
const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case LOGIN_REQ:
return {
...state,
loginPendding: true,
loginDone: false,
loginError: null
};
case LOGIN_SUCCESS:
return {
...state,
loginPendding: false,
loginDone: true,
userData: action.data
};
case LOGIN_FAIL:
return {
...state,
loginPendding: false,
loginError: action.error
};
}
};
export { LOGIN_REQ, LOGIN_SUCCESS, LOGIN_FAIL, loginRequestAction };
export default reducer;
μμμμ²λΌ κ°λ¨νκ² λ‘κ·ΈμΈ μμ²μ μν μνκ°μ λ³κ²½ν 리λμ λ‘μ§μ ꡬμ±νλ€. switch ꡬ문μμ κΈ°λ³Έμ μΈ λ¦¬λμ μ½λκ° μ₯ν©νκΈ° λλ¬Έμ immerμ κ°μ ν¨ν€μ§λ₯Ό μ΄μ©νκ±°λ createReducerμ κ°μ μ νΈλ¦¬ν° ν¨μλ₯Ό μ§μ ꡬνν΄μ μ¬μ©νλ©΄ μ’λ€. μ°μ μΌλ¨μ λ² μ΄μ§νκ² μ¬μ©νλ€.
리λμ€ μ¬κ°λ μ€ν μ΄λ₯Ό λ§λ€λ λ―Έλ€μ¨μ΄λ‘ μ°κ²°νκ³ , λμνλ ꡬ문μ λ£μ΄μ£Όλ©΄ λλ€. 그건 μ΄λ»κ² νλκ³ ? μλμ μ½λμ²λΌ νλ©΄λλ€.
// store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import user from './user';
import rootSaga from './saga';
// μ¬λ¬ μνκ°μ λ³κ²½νλ 리λμλ€μ νλμ 리λμ ν¨μλ‘ ν¨μΉλ€.
const rootReducer = combineReducers({ user });
// μ¬κ° λ―Έλ€μ¨μ΄λ₯Ό μμ±ν΄μ μ€ν μ΄μ μ°κ²°ν΄μ€λ€.
const sagaMiddleware = createSagaMiddleware();
// store μμ±
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
// μ¬κ° λ―Έλ€μ¨μ΄μμ ν΅ν© μ¬κ° ν¨μλ₯Ό μ€νμν¨λ€.
sagaMiddleware.run(rootSaga);
export default store;
νλμ© μ²μ²ν μμ보μ. 리λμ€ μ¬κ°μμ createSagaMiddleware
λ₯Ό λΆλ¬μμ μ¬κ° λ―Έλ€μ¨μ΄ κ°μ²΄λ₯Ό μμ±ν΄μ€λ€. μμ±λ μ¬κ° λ―Έλ€μ¨μ΄λ₯Ό 리λμ€ μ€ν μ΄μ applyMiddleware
ν¨μμ μΈμλ‘ λ겨주면 λ±λ‘λ μ€ν μ΄ μνκ°μ λ³κ²½ν λ μ¬κ° ν¨μλ€μ μΈμν μ€λΉκ° λμλ€. μ¬κ° λ―Έλ€μ¨μ΄ κ°μ²΄μ μλ run
λ©μλμ ν΅ν©μ μΈ rootSaga
ν¨μλ₯Ό μ°κ²°ν΄μ£Όλ©΄ μ λ§ μ¬κ°λ₯Ό μ¬μ©ν κΈ°λ³Έ μΈν
μ λμ΄λ€. μ΄μ λ¨μ κ²μ rootSagaλ₯Ό μμ±νλ¬ κ°λ μΌλΏμ΄λ€!
본격μ μΌλ‘ λΉλκΈ°μ μ‘μ μ μΈμνκ³ μνκ°μ λ³κ²½νκ² νλ μ¬κ° ν¨μλ€μ μμ±ν΄λ³΄μ. 리λμ€ μ€ν μ΄μ μ°κ²°ν rootSagaμ μ¬λ¬ μ¬κ° ν¨μλ€μ μ°κ²°ν΄μ£Όμ΄ μ¬κ° ν¨μκ° μ‘μ μ μΈμν μ μλλ‘ μ²λ¦¬ν΄μ£Όλ©΄ λλ€.
// saga/index.js
import { all, fork } from 'redux-saga/effects';
import userSaga from './user';
// yield ν€μλ λ€μ 리λμ€ μ¬κ°μ λΆμν¨κ³Ό ν¨μκ° μ¨λ€.
export default function* rootSaga() {
yield all([fork(userSaga)]);
}
리λμ€ μ¬κ°μ λΆμν¨κ³Ό ν¨μκ° λλΆλΆμ λΉλκΈ° μ²λ¦¬λ₯Ό νλ€κ³ 보면 λλ€. λΆμν¨κ³Ό ν¨μλ₯Ό μ¬μ©νλ €λ©΄ yieldλ‘ κ·Έ μ μ½μ κ±Έμ΄μ€ νμκ° μλ€. rootSaga μ λλ μ΄ν° ν¨μ λ΄λΆμ λ±λ‘λ yield ν€μλμλ all
μ΄λΌκ³ νλ λΆμν¨κ³Ό ν¨μκ° λΆλλ€. λ¨μ΄ λ» κ·Έλλ‘ ν¨μ λ΄λΆ λ°°μ΄μ λ±λ‘λ μ¬κ° ν¨μλ€μ 리λμ€ μ¬κ° λ―Έλ€μ¨μ΄μ λ±λ‘νλ λΆμν¨κ³Ό ν¨μλ€. λ±λ‘λ ν¨μκ° λμμ μ€νλ μ μλλ‘ μ²λ¦¬νλ€.
fork
λΆμν¨κ³Ό ν¨μλ μ‘μ
μ λ°μμν¨λ€. μ¬μ€ rootSagaμ all ν¨μ λ΄λΆμμ forkλ‘ μ¬κ° ν¨μλ₯Ό λ±λ‘ν΄λ λκ³ , λ°λ‘ νΈμΆνλ νμμΌλ‘ ꡬμ±ν΄λ λλ€. yield all([userSaga()])
μ΄λ°μμΌλ‘!
μ΄μ μ λ§ λ‘κ·ΈμΈ μ‘μ μ΄ λ°μν κ²μ κ°μ§νκ³ λΉλκΈ°μ μΈ μλ² μ²λ¦¬ μ΄νμ μλ΅μ λ°λΌμ 리λμ€ μνκ°μ λ³κ²½νλ userSaga ν¨μλ₯Ό μμ±ν΄λ³΄μ.
// login μμ²μ 보λ΄λ api
// api ν¨μλ μ μΌνκ² μ λλ μ΄ν° ν¨μκ° μλλ€!
function loginApi(data) {
return axios.post('/api/login', data);
}
// login μμ²μ λν μ‘μ
μ΄ κ°μ§λμμλ μνκ° μ²λ¦¬λ₯Ό μν΄ λμνλ μ¬κ° ν¨μ
function* loginRequest(action) {
const userData = yield call(loginApi, action.data);
try {
yield put({ type: LOGIN_SUCCESS, data: userData });
} catch (err) {
yield put({ type: LOGIN_FAIL, error: err.response.data });
}
}
// λ‘κ·ΈμΈ μμ²μ΄ λ€μ΄μ€λμ§λ₯Ό κ°μ§νλ μ λλ μ΄ν° ν¨μ
// addEventListener ν¨μμ κ·Έ μ¬μ©λ²μ΄ λΉμ·νλ€.
function* waitLogin() {
yield takeLatest(LOGIN_REQ, loginRequest);
}
function* waitLogin2() {
// μ λλ μ΄ν° λ¬Έλ²μ μ΄λ κ² λ¬΄νλ°λ³΅ ꡬ문λ yieldλ‘ μ μ΄ν μ μλ€.
while (true) {
yield take(LOGIN_REQ, loginRequest);
}
}
// userSaga ν¨μ λ±λ‘
export default function* userSaga() {
yield all([waitLogin()]);
}
userSaga
λΌκ³ νλ μ λλ μ΄ν° μ¬κ° ν¨μμ μμ λ§μ°¬κ°μ§λ‘ all λΆμν¨κ³Ό ν¨μκ° λ±λ‘λ κ²μ λ³Ό μ μλ€.
waitLogin
μ λλ μ΄ν° ν¨μλ takeLatest
λΌκ³ νλ μ¬κ° λΆμν¨κ³Ό ν¨μλ₯Ό λμ μν¨λ€. λμΌν μ‘μ
μ λν μμ²μ΄ μ¬λ¬λ² λ€μ΄μ¬ κ²½μ° κ°μ₯ μ΅κ·Ό μ¦, κ°μ₯ λ§μ§λ§ μμ²μ μ°μ ν΄μ μ²λ¦¬νλ λΆμν¨κ³Ό ν¨μλ€. λΆμν¨κ³Ό λ΄λΆμμ μ΄λ€ μ‘μ
μ κ°μ§ν μ§ λ±λ‘νκ³ , μ‘μ
μ΄ κ°μ§λλ©΄ λμμν¬ μ¬κ°ν¨μλ₯Ό λ±λ‘νλ€. ν¨μλ₯Ό μ‘μ
μ΄λ¦μ λ±λ‘ν΄μ μ¬μ©νλ λͺ¨μ΅μ΄ addEventListener
μ λΉμ·νλ€.
λ±λ‘λ loginRequest
μ λλ μ΄ν° ν¨μλ apiλ₯Ό μ§μ μ μΌλ‘ νΈμΆνλ€. yield ν€μλ λ€μ call
μ΄λΌκ³ νλ λΆμν¨κ³Ό ν¨μ λ΄λΆμμ apiκ° νΈμΆ λ κ²μ λ³Ό μ μλ€. λ¨μ΄ λ» κ·Έλλ‘ λΉλκΈ° μ‘μ
μ νΈμΆνλ€. λκΈ°μ μΈ μ²λ¦¬λ₯Ό νλ€. 첫 μΈμλ‘ λ±λ‘ν api ν¨μμ λλ²μ§Έ μΈμλ€μ νλΌλ―Έν°λ‘ λκΈ΄λ€. loginRequest ν¨μκ° μ€μ§μ μΌλ‘ μ»΄ν¬λνΈ λ‘μ§μ λ€μ΄κ°λ€λ κ²μ μ°λ¦¬λ μ μ μλ€.
loginRequest ν¨μμ try ~ catch
ꡬ문 λ΄λΆμμ μ‘μ
μ λμ€ν¨μΉ μν€λ κ²κ³Ό κ·Έ λ‘μ§μ΄ λΉμ·ν΄λ³΄μΈλ€. put
μ΄λΌκ³ νλ λΆμν¨κ³Ό ν¨μκ° μ‘μ
κ°μ²΄λ₯Ό μ
λ°μ΄νΈ ν΄μ£Όλ μν μ νλ€. μ§μ μ μΌλ‘ 리λμμ μμ±ν μ‘μ
μμ±μλ₯Ό λΆλ¬μλ λκ³ , μμ μ½λμ²λΌ μ‘μ
κ°μ²΄ μ체λ₯Ό λ§λ€μ΄μ€ μ μλ€.
κ·Έ μΈμ μ λ§ λ€μν 리λμ€ μ¬κ°μ λΆμν¨κ³Ό ν¨μκ° μλ€. μμ£Ό μ¬μ©νλ λΆμν¨κ³Ό ν¨μλ₯Ό λ§μ§λ§μΌλ‘ μ 리ν΄λ³Έλ€. λΆμν¨κ³Ό ν¨μ μμμλ yield ν€μλλ‘ μ μ½μ κ±Έμ΄μ€λ€λ κ²μ μμ§λ§μ
take
: 첫 μΈμλ‘ μ‘μ
μ λ±λ‘ν΄μ μ‘μ
μ΄ λ°μνλ κ²μ κ°μ§νλ€. μ‘μ
μ΄ λ°μνλ©΄ λλ²μ§Έ μΈμμ λ°μν ν¨μλ₯Ό νΈμΆνλ€. takeκ° prefix ννλ‘ λΆμ λΆμν¨κ³Ό ν¨μλ λ³΄ν΅ μ‘μ
μ κ°μ§νκ³ λ±λ‘ν ν¨μλ₯Ό λμμν¨λ€. νμ§λ§ μ‘μ
μ κ°μ§νλ κ²μ΄ λ¨λ°μ μ΄λ€.takeEvery
: take ν¨μμ μ¬μ©λ°©λ²μ λΉμ·νμ§λ§ λΉλκΈ°μ μΌλ‘ μ‘μ
μ κΈ°λ€λ¦°λ€. λ¨λ°μ μ΄μ§ μλ€.throttle
: λ±λ‘ν μκ°λ§νΌ μμ²μ λ³΄λΌ μ μλλ‘ μ νμ κ±°λ λΆμν¨κ³Ό ν¨μλ€.
λλ 리λμ€λ₯Ό μ¬μ©ν΄μ 리μ‘νΈ νλ‘μ νΈμ μ μμ μΈ μνκ°μ κ΄λ¦¬νλ©΄μ μ½λλ₯Ό μμ±νκ³ μλ€. λΉμ°ν apiλ₯Ό ν΅ν΄μ λΉλκΈ°μ μΈ μνκ° λ³κ²½μ μ²λ¦¬νλ λ‘μ§μ΄ μλ€. κ·Έλ μ£Όλ‘ λ¦¬λμ€ μ¬κ°λ₯Ό μ¬μ©νκ³ μλ€. λ무 λ§μ λΆμν¨κ³Ό ν¨μλ€μ΄ μμ΄μ μ μμ μ ν©ν λΆμν¨κ³Όλ₯Ό λ°μμν€λ κ²μ΄ μμ§λ μ΄λ ΅μ§λ§, μΌλ§λ μ°μμ§ μ’μμ§λ μ¬μ©ν λλ§λ€ μ§λ¦Ώνκ² λλΌκ³ μλ€.