๋ฆฌ๋์ค ์ฌ๊ฐ๋ ๋น๋๊ธฐ ์์ ์ฒ๋ผ ๋ฆฌ๋์์์ ์ฒ๋ฆฌํ๋ฉด ์๋๋ ์์ํจ์๊ฐ ์๋ ์์ ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ฆฌ๋์ค ๋ฏธ๋ค์จ์ด ์ ๋๋ค. Redux-Saga๋ ์ผ๋ฐ action์ dispatchํ๊ณ Generator๋ผ๋ ๊ฒ์ ํตํด function*๊ณผ ๊ฐ์ ๋ฌธ๋ฒ์ ์ฌ์ฉํฉ๋๋ค.
npm i redux-saga
๋ฆฌ๋์ค ์ฌ๊ฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ side Effect๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ๊ด๋ฆฌํ๊ณ , ์คํํ๊ธฐ ์ฝ๊ณ , ์ค๋ฅ๋ฅผ ๋ ์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋ชฉํ๋กํ๋ ๋ฆฌ๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๋๋ค.
Generators ๋ผ๋ ES6์ ๊ธฐ๋ฅ์ ์ฌ์ฉํด ๋น๋๊ธฐ ํ๋ฆ์ ์ฝ๊ฒ ์ฝ๊ณ ํ ์คํธ ํ ์ ์๋ค. ์ด๋ ๊ฒ ํ์ฌ ๋น๋๊ธฐ ํ๋ฆ์ ํ์ค ๋๊ธฐ JavaScript์ ์ฝ๋์ฒ๋ผ ๋ณด์ ๋๋ค.
์ฌ๊ธฐ์ Side Effect๋ ๋ถ์์ฉ์ด ์๋ ๋ถ์ ์์ฉ์ด๋ผ๊ณ ์๊ฐํด์ผํฉ๋๋ค.
๋ฆฌ๋์ค ์ฌ๊ฐ๋ฅผ ํ์ตํ๋ค๋ณด๋ฉด ๊ณ์ํด์ ๋ฐ๋ผ๋์ค๋ ํค์๋๊ฐ ์๋ค. ๋ํ์ ์ผ๋ก ์ ๋๋ ์ดํฐ์ ์ ๋๋ ์ดํฐํจ์์ด๋ค.
์ฐ์ ์ฌ๊ฐ๋ฅผ ์ง์ ์ ์ผ๋ก ๊ณต๋ถํ๊ธฐ์ ์ฌ๊ฐ์ ๊ธฐ๋ณธ ๋์์๋ฆฌ์ธ ์ ๋๋ ์ดํฐ์ ๊ด๋ จํ์ฌ ํ์ตํด์ผํ๋ค.
Redux-Saga์์ Saga๊ฐ ๋ฐ๋ก ์ ๋๋ ์ดํฐํจ์(์ ๋๋ ์ดํฐ ์๋)์ด๋ค.
๊ฐ๋จํ ๋งํ๋ฉด ์ ๋๋ ์ดํฐ๋ ์ ๋๋ ์ดํฐํจ์์ ๋ฐํ์ด๋ค.
function* generateFunction(){
yield 1;
yield 2;
yield 3;
}
const generator = generateFunction();
console.log(generator.next().value); //1
console.log(generator.next().value); //2
console.log(generator.next().value); //3
์์ ์ฝ๋์์ function*์ด ์ ๋๋ ์ดํฐํจ์(์ ๋๋ ์ดํฐ ์๋๋ค!)๋ค. ์ด ์ ๋๋ ์ดํฐ ํจ์๋ฅผ ํธ์ถ์ ๋ฐํ๋๋
๊ฐ์ฒด๊ฐ ์ ๋๋ ์ดํฐ ์ด๋ค. mdn๋ง ์ฐพ์๋ด๋ ์ฒซ์ค์ ์ ๋๋ ์ดํฐ ํจ์ ๋ฐํ๊ฐ์ด ์ ๋๋ ์ดํฐ๋ผ๊ณ ์๊ฐํ๋ค.
์ด ์ ๋๋ ์ดํฐ๋ ์๋์ ๊ฐ์ 3๊ฐ์ง ๋ฉ์๋๋ฅผ ๊ฐ์ง๋ค.
Generator.prototype.next()
Generator.prototype.return()
Generator.prototype.throw()
์ ๋๋ ์ดํฐ๋ ์ดํฐ๋ ์ดํฐ(Iterator) ํ๋กํ ์ฝ๊ณผ ์ดํฐ๋ฌ๋ธ(Iterable)ํ๋กํ ์ฝ์ ๋ฐ๋ฅธ๋ค.
๊ฐ๋จํ๊ฒ ์ดํฐ๋ฌ๋ธ๊ณผ ์ดํฐ๋ ์ดํฐ์ ๋ํด ์์๋ณด์.
Symbol.iterator
๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฐ์ฒด๋ฅผ ์๋ฏธํ๋ค. ์ดํฐ๋ฌ๋ธ ๊ฐ์ฒด๋ for of ๋ฌธ์ผ๋ก ์ํํ ์ ์๋ค.
๋ํ์ ์ผ๋ก๋ Array๊ฐ์ฒด๊ฐ์๋ค.
const array = [1,2,3,4,5];
console.log(Symbol.iterator in array); //true
for (const item of array ) {
console.log(item) //1,2,3,4,5
}
์ข๋ ์ค๋ช ํ์๋ฉด ์ดํฐ๋ฌ๋ธ ํ๋กํ ์ฝ์ ๋จ์ํ ์๋์ ๊ฐ์ด ํํ๋๋ค.
obj[Symbol.iterator]: Function => Iterator
๊ฐ์ฒด๋ ์ดํฐ๋ ์ดํฐ ์ฌ๋ณผ ํค๊ฐ์ ์ดํฐ๋ ์ดํฐ๋ฅผ ๋ฐํํ๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉด ์ดํฐ๋ฌ๋ธ์ด๋ค.
์ดํฐ๋ฌ๋ธ ๊ฐ์ฒด์์ Symbol.Iterator๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด iterator๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
๋ฐํ๋ iterator๊ฐ์ฒด๋ next๋ฉ์๋๋ฅผ ์์ ํ๊ณ ์์ผ๋ฉฐ IteratorResult๋ผ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
iteratorResult๋ value์ done ์ ํ๋กํผํฐ๋ก ๊ฐ์ง๋ค.
์ฌ๊ธฐ์ value๋ ํ์ฌ ์ํํ๋ ๊ฐ์ ๊ฐ๊ณ ์๊ณ done์ ์ํ๊ฐ ์ธ์ ๋๋๋์ง ์๋ ค์ค๋ค.
next๋ฉ์๋๋ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถ๋๋ค ๋ชจ๋ ์์๋ฅผ ์ํํ๋ฉด value๋ undefined๋ฅผ
doneํ๋กํผํฐ๋ true๊ฐ ๋๋ฉฐ ์ํ๋ฅผ ์ข ๋ฃํ๋ค.
const array = [1,2,3,4,5]
const iterator = array[Symbol.iterator]()
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 3, done: false }
console.log(iterator.next()); //{ value: 4, done: false }
console.log(iterator.next()); //{ value: 5, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
๋ค์ ์ ๋๋ ์ดํฐ, ์ ๋๋ ์ดํฐํจ์๋ก ๋์๊ฐ๋ณด์.
์์์ ์ธ๊ธํ๋ฏ ๋ฆฌ๋์ค ์ฌ๊ฐ์์ ์ฌ๊ฐ๋ ์ ๋๋ ์ดํฐ ํจ์์ด๊ณ ๋ฏธ๋ค์จ์ด๋ saga์๊ฒ yield๊ฐ์ ๋ฐ์ ๋๋ค๋ฅธ ์ด๋ค ๋์์ ์ํํ ์ ์๋ค.
์ฌ๊ฐ๋ ๋ฏธ๋ค์จ์ด์ ๋ช
๋ น์ ๋ด๋ฆฌ๋ ์ญํ ์ ํ๊ณ ์ค์ ๋น๋๊ธฐ ์์
์ ์ฒ๋ฆฌํด์ฃผ๋ ๋์์
๋ฏธ๋ค์จ์ด์์ ์ํํ๋ค.
์กฐ๊ธ๋ ์์ธํ ๋งํ๋ฉด ์ฌ๊ฐ๋ ๋ฏธ๋ค์จ์ด์ ์ดํํธ๋ฅผ ํ์ฉํด ์ด๋ ํ ๋น๋๊ธฐ ์์ ์ ํ๋๋ก ๋ช ๋ น์ ๋ด๋ฆฌ๋ ์ญํ ์ ํ๊ณ ์ง์ ์ ์ธ ๋น๋๊ธฐ ์์ ๋ฐ side Effect๋ฅผ ์ํํ๋ ๊ณณ์ ๋ฏธ๋ค์จ์ด์ด๋ฉฐ ์ด ๋ฏธ๋ค์จ์ด๊ฐ ๋์ํ ํ ๋ฐํ๋๋ ๋ฐํ๊ฐ์ด ๋ค์ ์ฌ๊ฐ์๊ฒ ๋์์ค๊ฒ ๋๋ ๊ฒ์ด๋ค.
์ด๋ ๊ธฐ ๋๋ฌธ์ saga์์ ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ์๋ฌด๋ฆฌ ๋ณต์กํด๋ ๋๋ถ๋ถ if, else, for์ ๊ฐ์ ๊ฐ๋จํ ์ฝ๋๋ก ๊ตฌํํ ์ ์๋ค.
์ดํํธ๋ ๋ฏธ๋ค์จ์ด์ ์ํด ์ํ๋์ผํ๋ ๋ช ๋ น์ ๋ด๊ณ ์๋ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด์ด๋ค.
์ดํํธ ์์ฑ์๋ ์ผ๋ฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ธฐ๋ง ํ๊ณ ์ด๋ ํ ๋์๋ ํ์ง์๋๋ค.
์ฌ๊ฐ๋ ์ด๋ฐ ๋ช ๋ น์ ๋ด๊ณ ์๋ ์ดํํธ๋ผ๋ ๊ฐ์ฒด๋ฅผ yieldํ ๊ฒ์ด๊ณ ๋ฏธ๋ค์จ์ด๋ ์ด๋ฌํ ๋ช ๋ น์ ๋ฐํ์ผ๋ก ๋์ํ๊ณ ๊ทธ๊ฒฐ๊ณผ๋ฅผ ์ฌ๊ฐ์๊ฒ ๋๋ ค์ฃผ๋ ๊ฒ์ด๋ค. ์ด์ ์ด๋ค ์ดํํธ ์ข ๋ฅ๊ฐ ์๋์ง ํ์ธํด ๋ณด์.
takeEvery(action, sagaFn)
takeLatest(action, sagaFn)
select
put
take
call(fn,...args)
fork
join
ํ๋์ saga๊ฐ ์คํ๋๋ ๊ฒ์ ํ์คํฌ๋ผ๊ณ ํ๋ค.
์ฌ๊ฐ๋ฅผ ํ์ฉํด์ ๋ช๊ฐ์ง ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ํด๋ณด๋๋ก ํ๊ฒ ๋ค.
์ฒซ๋ฒ์งธ๋ก ๋ ์จ API๋ฅผ ๋ถ๋ฌ์ค๊ฒ ๋ค.
// store/modules/weather.js
import { createAction, createReducer } from '@reduxjs/toolkit';
export const GET_WEATHER = 'weather/GET_WEATHER';
export const GET_WEATHER_SUCCESS = 'weather/GET_WEATHER_SUCCESS';
export const GET_WEATHER_FAILURE = 'weather/GET_WEATHER_FAILURE';
export const GET_WEATHER_LOADING = 'weather/GET_WEATHER_LOADING';
export const getWeather = createAction(GET_WEATHER);
export const getWeatherSuccess = createAction(GET_WEATHER_SUCCESS);
export const getWeatherFailure = createAction(GET_WEATHER_FAILURE);
export const getWeatherLoading = createAction(GET_WEATHER_LOADING);
const initialState = {
isLoading: false,
weatherData: []
};
const reducer = createReducer(initialState, {
[getWeatherLoading]: (state) => {
state.isLoading = true;
},
[getWeatherSuccess]: (state, action) => {
(state.isLoading = false), (state.weatherData = action.payload);
},
[getWeatherFailure]: (state) => {
state;
}
});
export default reducer;
์ก์ ๊ณผ ๋ฆฌ๋์์๋ ํน๋ณํ ๋์ ๋๋๊ฒ ์๋ค.
๋ ์จAPI๋ฐ์์ฌ๋ ์ฑ๊ณต,์คํจ,๋ก๋ฉ์ํฉ์ ๋ํด ์ก์ ๊ณผ ๋ฆฌ๋์๋ฅผ ์์ฑํด์ฃผ์๋ค.
// store/api.js
// weather API
const API = {
key: '062f94b6879d4a4a64755999bee3a513',
base: 'https://api.openweathermap.org/data/2.5/'
};
export function getWeatherApi() {
return axios.get(`${API.base}${query}${API.key}`);
}
์์ ์ฝ๋์์ ํด๋น API๋ฅผ fetchingํ๋ค.
// store/sagas/weather.js
import { put, call, takeEvery, fork } from 'redux-saga/effects';
import * as actions from '../modules/weather';
import { getWeatherApi } from '../api'; --(1)
function* getWeather() {
try {
const response = yield call(getWeatherApi); --(2)
yield put(actions.getWeatherSuccess(response));
} catch (err) { --(3)
yield put(actions.getWeatherFailure(err));
}
}
function* watchGetWeather() {
yield takeEvery(actions.GET_WEATHER, getWeather); --(4)
}
export default function* watchSaga() {
yield fork(watchGetWeather);
}
(1) : API.js์์ api๋ฅผ fetchingํ๋ ํจ์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
(2) : Promise์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ธฐ์ํด ๊ด๋ จ ์ดํํธ์ธ call์ ์ฌ์ฉํ์์ต๋๋ค.
(3) : api fetching์คํจํ์๋์ ์ํฉ์ ์ํด ์์ฑํ ๋ฆฌ๋์๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
(4) : getWeather์์ ๋ฏธ๋ค์จ์ด์์ ์ํํ ๊ฒฐ๊ณผ ๋ฐํ ๊ฐ์ (4)์์ ๋ค์ ๋๋ ค๋ฐ์ต๋๋ค.
// store/sagas/index.js
import { all, fork } from 'redux-saga/effects';
import weather from './weather';
import ...
export default function* rootSaga() {
yield all([fork(weather), ...]);
}
combineReducer์ฒ๋ผ saga์ปดํฌ๋ํธ๊ฐ ๋ง์์ง๋ฉด ์ด๋ฐ์์ผ๋ก ๋ฃจํธ ์ฌ๊ฐ์ ํ๋ฒ์ ๋ด์์ ์คํ ์ด์ ๋ณด๋ ๋๋ค.
//store/store.js
import createSagaMiddleware from 'redux-saga';
import logger from 'redux-logger';
import index from './modules/index';
import rootSaga from './sagas/index';
import { configureStore } from '@reduxjs/toolkit';
const sagaMiddleware = createSagaMiddleware();
const middlewares = [logger, sagaMiddleware];
export const store = configureStore({
devTools: false,
middleware: middlewares,
reducer: index
});
sagaMiddleware.run(rootSaga);
์ด๋ฐ์์ผ๋ก rootSaga๋ฅผ store๋ก importํด์์ ๋ฏธ๋ค์จ์ด ๋์์ ์ํด createMiddleware ๊ทธ๋ฆฌ๊ณ
๋ณด๋ค ๋ช ํํ ๊ฐ๋ฐ์ ๋๊ตฌ์์ ํ์ธ์ ์ํด logger๋ฅผ importํด์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ํ sagaMiddleware.run(rootSaga)๋ฅผ ํตํ์ฌ ๋ชจ๋ ์ฌ๊ฐ ์ปดํผ๋ํธ ๊ฒฐ๊ณผ๋ฅผ ์คํ์ํต๋๋ค.
์คํ์ค์ธ ํ๋ก์ ํธ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ์ฝ์์ฐฝ์์ ๋ฆฌ๋์ค ๋ก๊ฑฐ๋ฅผ ํตํ์ฌ ํ์ธํ ์ ์์ต๋๋ค.
์์ ๊ฐ์ด ์ํ๋ weather API data์ ๋ณด๊ฐ ๋ค์ด์ค๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.