kuy๋์ด ์ฐ์ redux-sagaใง้ๅๆๅฆ็ใจๆฆใ๋ผ๋ ๊ธ์ ๋ฒ์ญ์ ๋๋ค. ๋ณธ ๋ฒ์ญ ๊ธ์ ์ ์์์ ํ๊ฐํ์ ์์ฑ๋์ด ์์ต๋๋ค.
๋ํ, ์ผ๋ณธ์ด์ ํน์ฑ์ ํ๊ตญ์ด์์๋ ์ ์์ฐ๋ ํ์์ด๊ฐ ๋ง๊ณ ์ง์ญํ ๊ฒฝ์ฐ, ๊ฐ๋ ๋์์ค๊ฐ ์ ๋งคํด์ง๊ฑฐ๋ ํํ์ด ์ฅํฉํด์ง๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ์, ์ด๋ฌํ ๋ถ๋ถ์ ์๋๊ฐ ๋ณํ์ง ์๋ ์ ์์ ์์ญ์ ํ๊ณ ์์ต๋๋ค.
๋ฒ์ญ์ : Junyoung Choi (Rokt33r)
Redux๋ ๋จ์ผ Store, ๋ถ๋ณ์ ์ธ State, Side effect๊ฐ ์๋ Reducer์ 3๊ฐ์ง ์์น์ ๋ด์ธ์ด Flux ํ๋ ์์ํฌ์ ๋๋ค. ํ์ง๋ง ๋ค๋ฅธ ํ๋ ์์ํฌ์๋ ๋ฌ๋ฆฌ ์ ๊ณตํ๋ ๊ฒ์ ์ต์ํ์ผ๋ก, Fullstack์ด๋ผ๊ณ ๋ ๋งํ๊ธฐ ํ๋ค ๋งํผ ์์ต๋๋ค. ๊ทธ ๋๋ฌธ์ ๋ชจ๋ ๋ฉด์ ์์ด์ ์ผ๋ฐ์ ์ด๋ผ๊ณ ํ ๋งํ ์ฌ์ฉ๋ฒ์ด ์ ๋ฆฌ๋์ง ์์์, ์ด๋ป๊ฒ ์ฌ์ฉํด์ผํ ์ง ๋ฐฉํฉํ๋ ๊ฒฝ์ฐ๋ ์ ์ง ์์ต๋๋ค. ๊ทธ ํ๋๋ก ๋งํ ์ ์๋๊ฒ ๋น๋๊ธฐ์ฒ๋ฆฌ์ ๋๋ค. ์ปค๋ฎค๋ํฐ๋ ์ง๊ธ๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ ๋ชจ์ํ๊ณ ์๊ณ , ์ฃผ๋ก ์ฐ์ฌ์ง๋๊ฑด redux-thunk์ redux-promise์ ๋ ์ผ๊ฒ๋๋ค. Redux๋ก ํ์ ํ์ง ์๋๋ค๋ฉด react-side-effect๋ ์์ต๋๋ค. ์ด๊ฑด Twitter์ ๋ชจ๋ฐ์ผ ๋ฒ์ ผ์ ์ฌ์ฉ๋๊ณ ์์ต๋๋ค. ์ด๋ค ๊ฑธ ์จ๋ ๋น๋๊ธฐ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๊ฒ ๋์ง๋ง, ๊ทธ๊ฒ๋ค์ ์ด๋๊น์ง๋ ๋๊ตฌ๋ก์จ, ์ค๊ณ์ ์ง์นจ๊น์ง๋ ์๋ ค์ฃผ์ง ์์ต๋๋ค. ๋ฌธ์ ๋ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ ์ด๋์ ์ธ ๊ฒ ์ธ๊ฐ, ์ด๋ป๊ฒ ์ธ ๊ฒ ์ธ๊ฐ, ๊ทธ๋ฆฌ๊ณ ์ด๋์ ๋ถ๋ฌ์์ผ ํ๋๊ฐ ์ ๋๋ค. Redux๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ํฉ์ด ๊ณ ๋ฏผํ๊ณ ์์ง๋ ์์ผ์ ๊ฐ์?
- ํน์ Action์ ๊ธฐ๋ค๋ฆฌ๋ค ๋ค๋ฅธ Action์ dispatchํ๋ค.
- ํต์ ์ฒ๋ฆฌ๋ฅผ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ณ , ๋ค๋ฅธ ํต์ ์ฒ๋ฆฌ๋ฅผ ์์ํ๋ค.
- ์ด๊ธฐํ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด๋ค์ด๊ณ ์ถ๋ค.
- ๋น๋ฒํ ๋ฐ์ํ๋ Action์ ๋ชจ์์ dispatchํ๊ณ ์ถ๋ค.
- ๋ค๋ฅธ ํ๋ ์์ํฌ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์ด์ธ๋ฆฌ๊ฒ ํ๊ณ ์ถ๋ค.
React + Redux์ ์๋ฆ๋ค์ด ์ธ๊ณ์์ ์์ธ ๋ฌ๋๋ ๊ทธ ์ฝ๋๋ค์ ์ด๋ป๊ฒ ํด์ผ ํ ๊ฒ์ธ๊ฐ. ์ด๋ป๊ฒ ์ธ์์ผ ํ ๊ฒ์ธ๊ฐ. ์ด ๊ธ์๋ ํ๊ฐ์ง์ ํด๊ฒฐ๋ฐฉ๋ฒ์ผ๋ก์จ redux-saga๋ฅผ ์๊ฐํฉ๋๋ค. redux-saga์ ๊ฐ์์ ๊ธฐ๋ณธ์ ์ธ ๊ฐ๋ ์๋ํด ๊ฐ๋ณ๊ฒ ์ค๋ช ํ๊ณ , ์ต์ํ redux-thunk๋ก ๋ง๋ค์ด์ง ์ฝ๋์ ๋น๊ตํด๋ณด๊ฒ ์ต๋๋ค. ์กฐ๊ธ๋ง ์ด๋ณด์ ์ธ ์ ์ ๋ฐฉ๋ฒ๊ณผ ์ค์ํ๊ธฐ ์ฌ์ด ๋ถ๋ถ์ ์ค๋ช ํ๊ณ , ํ๋ฐ์ ์ค์ ์ ์ธ redux-saga์ ์ฌ์ฉ๋ฒ์ ์๊ฐํฉ๋๋ค.
์ฐธ๊ณ ๋ก ๊ณต์ ๋ฆฌํฌ์งํ ๋ฆฌ์์ ํ๊ตญ์ด๋ก๋ README๊ฐ ์ ๊ณต๋๊ณ ์์ต๋๋ค. ์ผ๋จ ์จ๋ณด๊ณ ์ถ๋ค! ๋ผ๋ ๋ถ๋ค์ ๋จผ์ ์ด์ชฝ ๋งํฌ๋ ํ์ด๋ด์ฃผ์ธ์.
redux-saga๋ Redux๋ก Sideeffect๋ฅผ ๋ค๋ฃจ๊ธฐ ์ํ Middleware์ ๋๋ค. ... ์๋ง ์ด๋๋ก๋ผ๋ฉด ์ดํดํ๊ธฐ ์ด๋ ต๊ฒ ์ง์. ์ด ๊ตฌ๋ฌธ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์งง์ ์ค๋ช ๋ฌธ์ด์ง๋ง, ์ค์ ๊ทธ๋ค์ง ๋ณธ์ง์ ํํํ๊ณ ์์ง ๋ชปํฉ๋๋ค. ๊ทธ๋ฐ ์ด์ ๋ก ์ ๋๋ฆ์ ์ดํด์ ๋จ์ด๋ก ์ค๋ช ํด๋ณด๊ฒ ์ต๋๋ค.
redux-saga๋ "Task"๋ผ๋ ๊ฐ๋ ์ Redux๋ก ๊ฐ์ ธ์ค๊ธฐ์ํ ์ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ฌ๊ธฐ์ ๋งํ๋ Task๋ ์ผ์ ์ ์ฐจ์ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์คํ ๋จ์๋ก์จ, ๊ฐ๊ฐ ํํ์ ์ผ๋ก ์๋ํฉ๋๋ค. redux-saga๋ ์ด Task์ ์คํํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค. ๋๋ถ์ด ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ Task๋ก์จ ๊ธฐ์ ํ๊ธฐ ์ํ ์ค๋น๋ฌผ์ธ "Effect"์ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ ๋๊ธฐ์ ์ผ๋ก ํํํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค. Effect๋ Task๋ฅผ ๊ธฐ์ ํ๊ธฐ ์ํ ์ปค๋งจ๋(๋ช ๋ น, Primitive)์ ๊ฐ์ ๊ฒ์ผ๋ก, ์๋ฅผ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒ๋ค์ด ์์ต๋๋ค.
select
: State๋ก๋ถํฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ธ๋ค.put
: Action์ dispatchํ๋ค.take
: Action์ ๊ธฐ๋ค๋ฆฐ๋ค. ์ด๋ฒคํธ์ ๋ฐ์์ ๊ธฐ๋ค๋ฆฐ๋ค.call
: Promise์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.fork
: ๋ค๋ฅธ Task๋ฅผ ์์ํ๋ค.join
: ๋ค๋ฅธ Task์ ์ข ๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.- ...
์ด๋ฌํ ์ฒ๋ฆฌ์ค์๋ Task์์ ์ง์ ์คํํ ์ ์๋ ๊ฒ๋ ์์ง๋ง, redux-saga์๊ฒ ๋ถํํ์ฌ ๊ฐ์ ์ ์ผ๋ก ์คํํฉ๋๋ค. ์ด๋ก์จ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ co์ฒ๋ผ ๋๊ธฐ์ ์ผ๋ก ์ธ ์ ์๊ฒ ํ๊ณ , ๋ณต์์ Task๋ฅผ ๋์ํํ์ ์ผ๋ก ์คํํ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค. ๋ค์ ๊ทธ๋ฆผ์ redux-saga์์ ์คํ๋๋ Task์ ์ด๋ฏธ์ง์ ๋๋ค.
Flux๋ Redux๋ง์ผ๋ก๋ ๋ํดํ๋๊ฒ์ด ๋์ฑ ์๋ก์ด ๊ฐ๋ ์ ๊ฐ์ ธ์์ ํผ๋์ค๋ฝ๊ฒ ํ๊ณ ์๋ค์. ๊ทธ๋๋ Redux-saga๋ฅผ ์ธ ๊ฐ์น๋ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
- Mock ์ฝ๋๋ฅผ ๋ง์ด ์ฐ์ง ์์๋ ๋๋ค.
- ์์ ์ฝ๋๋ก ๋ ๋ถํ ํ ์ ์๋ค.
- ์ฌ์ด์ฉ์ด ๊ฐ๋ฅํด์ง๋ค.
์ด๊ฒ์ ๋จ์ํ "์ฌ์ด์ฉ๊ฐ๋ฅ"์ด๋ผ๋ ๋จ์ด ์ด์์ ์๋ฏธ๊ฐ ์์ต๋๋ค. ๋ฌด์จ ๋ง์ด๋๋ฉด ์ฌ์ด์ฉ๊ฐ๋ฅํ Container Component๋ฅผ ๊ฐ๋ฐํ๋๋ฐ ์์ด ํ์์ ์ธ ์์์ ๋๋ค. Middleware๋ผ๋ ์ ๋ง ์ดํดํ๊ธฐ ํ๋ค์ง๋ง ์ ๊ฒฝ์ธ ์ ๋ฐ์ ์๋ ๊ฒ์ด ์ฐ๋๋ฏธ์ฒ๋ผ ์๊ณ , ๋์ฑ์ด ์ฌ์ด์ฉ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ก์จ ๋์ ํ ๋๋, ์ด๋์ Middleware๋ฅผ ๋ฃ์ด์ค ๊ฒ์ธ๊ฐ๋ฅผ ์๊ฐํ์ง์์ผ๋ฉด ์๋ฉ๋๋ค. ๋ฐ๋ฉด, saga๋ผ๋ฉด ์์น์ ์ผ๋ก ์๋ก ๋ ๋ฆฝ์ ์ ๋์ํ๊ธฐ ๋๋ฌธ์ ์์ ์ ์ธ๊ณ์์๋ง ์ฝ๋๋ฅผ ์ฐ๋๊ฒ ๊ฐ๋ฅํ์ฌ, ๋ค๋ฅธ saga์ ์ํฅ์ ๋ผ์น์ง ์์ต๋๋ค.
์ถ์์ ์ธ ์ค๋ช ์ ๊ทธ๋ค์ง ์ดํด๋๊ฐ ๋์์ง์ง ์๊ธฐ์, redux-thnk๋ก ์ด ์ฝ๋์ ๋น๊ตํ๋ฉฐ redux-saga๋ก ์ธํด ์ด๋ป๊ฒ ๋ฐ๋๋์ง๋ฅผ ํ๋ฒ ๋ด ์๋ค.
์ํ๋ก์จ FetchAPI๋ฅผ ์ฌ์ฉํ ํต์ ์ฒ๋ฆฌ๋ฅผ ํด๋ด ์๋ค.
๋ฐ์ดํฐ๋ฅผ ์ฝ์ด๋ค์ด๋๊ฑด ๊ฐ๋จํ์ง๋ง, Redux๋ก ์ ๋๋ก ์๊ฐํด์ผํ ๊ฒ ์ ์ง ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ ๋ค์ ๋๋ค.
- ์ด๋์ ํต์ ์ฒ๋ฆฌ๋ฅผ ์ ์ ๊ฒ์ธ๊ฐ
- ์ด๋๋ก๋ถํฐ ํต์ ์ฒ๋ฆฌ๋ฅผ ๋ถ๋ฌ์ฌ ๊ฒ์ธ๊ฐ
- ํต์ ์ฒ๋ฆฌ์ ์ํ๋ฅผ ์ด๋ป๊ฒ ๊ฐ์ง๊ฒ ํ ๊ฒ์ธ๊ฐ.
3๋ฒ์งธ ์์๋ redux-thunk๋ redux-saga ๋๋ค ๊ณตํต์ ์ธ ๋ถ๋ถ์ด๋ฏ๋ก ๋จผ์ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
ํต์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๊ธฐ๊น์ง "์ฝ์ด๋ค์ด๋ ์ค..."์ด๋ผ๋ ๋ฉ์ธ์ง๋ฅผ ํ์ํ๊ธฐ ์ํด์๋, ํต์ ์ํ๋ฅผ Store๋ก ๊ฐ์ง๊ฒ ํ ๋ค์์, ํต์ ์ ๊ฐ์/์ฑ๊ณต/์คํจ์ 3 ํ์ด๋ฐ์ Action์ dispatchํ์ฌ ์ํ๋ฅผ ๋ฐ๊ฟ ํ์๊ฐ ์์ต๋๋ค. ์ด ์ ์ฉํจํด์ธ Redux์ ์ํ ์ฝ๋๊ฐ ์๋ง ์๋ณธ์ผ๋ก, ์ด๊ฒ๋ง ๋ฝ์๋ธ redux-api-middleware๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ง๋ง, ์ด๋ฒ์ Middleware๋ redux-api-middleware๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ฐ์ฌ์์ต๋๋ค. ํต์ ์ํ๋ฟ๋ง ์๋๋ผ, ํต์ ์ด ์ ์์ ์ผ๋ก ์ข ๋ฃ๋์๋๊ฐ, ์๋ฌ๋ก ์ธํด ์ข ๋ฃ๋์๋๊ฐ๊น์ง ๋ง์ถ์ด ๋ฃ์ด๋๋ฉด ์๋ฌ๋ฉ์ธ์ง๋ฅผ ํ์ํ๋๋ฐ ์ธ ์ ์์ด์ ํธ๋ฆฌํฉ๋๋ค.
์ํ ์ฝ๋๋ 3๊ฐ์ง์ Action ํ์
REQUEST_USER
, SUCCESS_USER
, FAILURE_USER
์ ๋ฌธ์์ด ์์์ Action ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ๊ธฐ์ํ 3๊ฐ์ง์ Action Creator requestUser
, successUser
, failureUser
๋ actions.js
์ ์ ์๋์ด ์์ต๋๋ค.
๊ทธ๋ผ redux-thunk ์ฝ๋๋ฅผ ๋ด ์๋ค.
api.js
export function user(id) {
return fetch(`http://localhost:3000/users/${id}`)
.then(res => res.json())
.then(payload => ({ payload }))
.catch(error => ({ error }));
}
actions.js
export function fetchUser(id) {
return dispatch => {
dispatch(requestUser(id));
API.user(id).then(res => {
const { payload, error } = res;
if (payload && !error) {
dispatch(successUser(payload));
} else {
dispatch(failureUser(error));
}
});
};
}
๋จผ์ ์ฒ๋ฆฌ ์ ์ฒด์ ํ๋ฆ์ ํ์ธํด๋ด ์๋ค.
- (๋๊ตฐ๊ฐ๊ฐ
fetchUser
ํจ์์ ๋ฆฌํด ๊ฐ์ dispatchํ๋ค) - redux-thunk์ Middleware๊ฐ dispatch๋ ํจ์๋ฅผ ์คํํ๋ค.
- ํต์ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ํ๊ธฐ ์ ์
REQUEST_USER
Action์ dispatchํ๋ค. API.user
ํจ์๋ฅผ ๋ถ๋ฌ๋ด์ ํต์ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ํ๋ค.- ์๋ฃ๋๋ฉด
SUCCESS_USER
ํน์FAILURE_USER
Action์ dispatchํ๋ค.
api.js
์ user
ํจ์๋ ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์์ด๋ค. Fetch API๋ Promise๋ฅผ ๋๋ ค์ฃผ๊ธฐ์ ์ ์ ํ ์ฒ๋ฆฌํ ํ์๊ฐ ์์ต๋๋ค. ์๋ฌํธ๋ค๋ง์ ๋ฐฉ๋ฒ์ ์ํ๋ ๋๋ก ํ์
๋ ๋์ง๋ง, ์ฌ๊ธฐ์ try/catch
๋ฅผ ์ฐ์ง ์๊ณ , ๋ฆฌํด ๊ฐ์ผ๋ก ํ์ ํ๋ ์คํ์ผ์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค.
actions.js
์ fetchUser
ํจ์๋ Action Creator์ด์ง๋ง, redux-thunk๋ก ๋ถํฐ ์คํ๋๊ธฐ ์ํด์ , Action ์ค๋ธ์ ํธ๊ฐ ์๋๋ผ ํจ์๋ฅผ ๋๋ ค์ค๋๋ค. redux-thunk๋ dispatch๋ฟ๋ง ์๋๋ผ getState๋ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ์ง๋ง, ์ง๊ธ์ ํ์ํ์ง ์์ผ๋ฏ๋ก ์๋ตํฉ๋๋ค. ์ข ์ ์ ํต์ ์ฒ๋ฆฌ์ ํจํด์ ๋ฐ๋ผ ์ฒ์์๋ REQUEST_USER
Action์ dispatchํ๊ณ , ์๋ฃํ๊ฑฐ๋ ์คํจํ๋ฉด SUCCESS_USER
ํน์ FAILURE_USER
Action์ dispatchํฉ๋๋ค. ์ด๋ ๊ฒ redux-thunk๋ฅผ ์ฌ์ฉํ๋ฉด ๋น๋๊ธฐ์ฒ๋ฆฌ ์ฝ๋๋ฅผ Action Creator์๋ค ์ ๊ฒ ๋ฉ๋๋ค. ๋ณธ๋์ Action Creator๋ Action ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ์ฌ ๋๋ ค์ค ๋ฟ์ด์๊ธฐ์,์์ฑํ Action ์ค๋ธ์ ํธ๋ฅผ dispatchํ๋ ๋ฐ๋ค๊ฐ, ์๋ค๋ก ์ด๋ฐ์ ๋ฐ ๋ก์ง์ด ๋ค์ด๊ฐ๋๊ฑด ์ํํ ๋์๊ฐ ๋ฉ๋๋ค. ๋์
๋ ์ฌ์ฉ๋ฒ๋ ๊ฐ๋จํ ๋ฐ๋ฉด, ์ฌํํ์
๋ ์๋์ฑ๋ก ํธํ๋๊น ๋ง์ฐ๋ค๊ฐ ๋์ค์ ์ง์ฅ์ ๋ง์ดํ ์ง๋ ๋ชจ๋ฆ
๋๋ค. ์๊ทน์ ์ผ๋ก ์ฌ์ฉํ๋ค๋ฉด ๋ฌธ์ ๊ฐ ์๊ฒ ์ง๋ง, ๋ณต์กํ ํต์ ์ฒ๋ฆฌ๋ ์ ๋ง๋ก ์ฐ๊ณ ์ถ์ง ์์ต๋๋ค. ์ฐ๊ณ ์ถ๋ค๋ ์๊ฐ๋ ์๋ฉ๋๋ค.
๊ทธ๋ผ redux-saga๋ก ๋ฐ๊ฟ์ฐ๋ฉด ์ด๋ป๊ฒ ๋๋์ง ๋ด ์๋ค.
sagas.js
function* handleRequestUser() {
while (true) {
const action = yield take(REQUEST_USER);
const { payload, error } = yield call(API.user, action.payload);
if (payload && !error) {
yield put(successUser(payload));
} else {
yield put(failureUser(error));
}
}
}
export default function* rootSaga() {
yield fork(handleRequestUser);
}
๋ฐ๋๊ฐ ๋์ ์ฝ๋์ด๋ฏ๋ก ๊ธฐํฉ์ ๋ฃ๊ณ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๋จผ์ ์ ์ฒด์ ์ธ ํ๋ฆ๋ถํฐ..
- redux-saga์ Middleware๊ฐ
rootSaga
Task๋ฅผ ์์์ํจ๋ค. fork
Effect๋ก ์ธํดhandleRequestUser
Task๊ฐ ์์๋๋ค.take
Effect๋กREQUEST_USER
Action์ด dispatch๋๊ธธ ๊ธฐ๋ค๋ฆฐ๋ค.- (๋๊ตฐ๊ฐ๊ฐ
REQUEST_USER
Action์ dispatchํ๋ค.) call
Effect๋กAPI.user
ํจ์๋ฅผ ๋ถ๋ฌ์์ ํต์ ์ฒ๋ฆฌ์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.- (ํต์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ค.)
put
Effect๋ฅผ ์ฌ์ฉํ์ฌSUCCESS_USER
ํน์FAILURE_USER
Action์ dispatchํ๋ค.- while ๋ฃจํ์ ๋ฐ๋ผ 3๋ฒ์ผ๋ก ๋์๊ฐ๋ค.
redux-thunk๋ก ์ธํ ์ฝ๋์์ ๋น๊ต๋ฅผ ์ํด ์ผ๋ จ์ ํ๋ฆ์ ์จ๋ณด์์ง๋ง, ์ค์ ์ด ์ฒ๋ฆฌ๋ก๋ ๋์๋ณํํ์ฌ ์์ง์ด๋ 2๊ฐ์ ํ๋ฆ์ด ์์ต๋๋ค. ๊ทธ๊ฒ์ด Task์
๋๋ค. sagas.js
๋ก ์ ์๋ 2๊ฐ์ง ํจ์ ๋ชจ๋ redux-saga์ Task์
๋๋ค. ํ๋์ฉ ์ดํด๋ด
์๋ค.
rootSaga
Task๋ Redux์ Store๊ฐ ์์ฑ ๋ ํ, redux-saga์ Middleware๊ฐ ๊ธฐ๋ ๋ ๋ 1๋ฒ๋ง ๋ถ๋ฌ์์ง๋๋ค. ๊ทธ๋ฆฌ๊ณ fork
Effect์ ์ฌ์ฉํ์ฌ redux-saga์๊ฒ ๋ค๋ฅธ Task๋ฅผ ๊ธฐ๋ํ ๊ฒ์ ์์ฒญํฉ๋๋ค. ์์ ์ค๋ช
ํ๋ฏ์ด, Task๋ด์๋ ์ค์ ์ฒ๋ฆฌ๋ฅผ ํํ์ง ์์ผ๋ฏ๋ก, fork
ํจ์๋ก๋ถํฐ ์์ฑ๋ ๊ฒ์ ๋จ์ํ ์ค๋ธ์ ํธ์
๋๋ค. ์ด๊ฒ์ Flux ์ํคํ
์ณ์ Action ์ค๋ธ์ ํธ์ ๊ฐ๊น์ด ๋๋์
๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ๋ค์๊ณผ๊ฐ์ด ์ค๋ธ์ ํธ์ ๋ด์ฉ์ ๋ณด๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค.
console.log(fork(handleRequestUser));
์ด๋ฅผ ์คํํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ ๋๋์ ์ค๋ธ์ ํธ๊ฐ ์์ฑ๋ฉ๋๋ค.
{
Symbol<IO>: true,
FORK: {
context: ...,
fn: <func>,
args: [...]
}
}
์ผ๋จ, Effect ์ค๋ธ์ ํธ๋ ์์ฑ๋ ๋ฟ ์๋ฌด๊ฒ๋ ํ์ง์์ผ๋ฏ๋ก, redux-saga๋ก ๋๊ฒจ์ ธ์ ์คํ๋ ํ์๊ฐ ์์ต๋๋ค. ์ด ์คํ์ ์ํด์๋ Generator ํจ์์ yield
๋ฅผ ์ฌ์ฉํด ๋ถ๋ฌ์ค๋ ์ฝ๋ ๊ฐ์ ๋๊ฒจ์ฃผ๊ณ ์์ต๋๋ค. "๋ถ๋ฌ์ค๋ ์ชฝ์ ์ฝ๋"๋ ๋๊ตฌ์ ๊ฒ์ธ๊ฐ? ๊ทธ๊ฒ์ redux-saga๊ฐ ์ ๊ณตํ๋ Task์ ์คํํ๊ฒฝ์ธ Middleware์
๋๋ค. Effect ์ค๋ธ์ ํธ๋ฅผ ๋ฐ์๋ค์ธ redux-saga์ Middleware๋ ๋๊ฒจ์ง ํจ์๋ฅผ ์๋ก์ด Task๋ก์จ ๊ธฐ๋์ํต๋๋ค. ๊ทธ ์ดํ redux-saga๋ 2๊ฐ์ Task๊ฐ ๋์์ ์์ง์ด๊ณ ์๋ ์ํ๊ฐ ๋ฉ๋๋ค. ์๋กญ๊ฒ ๊ธฐ๋๋ handleRequestUser
Task์ ์ค๋ช
์ผ๋ก ๋์ด๊ฐ๊ธฐ ์ ์ rootSaga
Task์ "๊ทธ ์ดํ"๋ฅผ ๋ฐ๋ผ๊ฐ๋๋ค.
fork
Effect๋ ์ง์ ํ Task์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก yield
๋ ๋ธ๋ก๋์ง์๊ณ ๊ณง ์ ์ด๋ก ๋์์ต๋๋ค. ํ์ง๋ง rootSaga
Task๋ handleRequestUser
Task์ ๊ธฐ๋ ์ด์ธ์ ํ ์ผ์ด ์์ต๋๋ค. ๊ทธ๋๋ฌธ์ rootSaga
Task๋ด์๋ fork
๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ๋๋ ๋ชจ๋ Task๊ฐ ์ข
๋ฃ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ์ด๋ฌํ ์์ง์์ redux-saga v0.10.0๋ถํฐ ๋์
๋ ์๋ก์ด ์คํ๋ชจ๋ธ๋ก ์ธํ ๊ฒ์ผ๋ก, ์ฐ์์ ์ธ Task์ ์ทจ์๋ฅผ ๊ตฌํํ๊ธฐ์ํด ํ์ํ์ต๋๋ค. ์ด๊ฒ์ ๋ถ๋ชจ Task, ์์ Task, ์์ Task 3๊ฐ์์, ๋ถ๋ชจ๊ฐ ์์์ Forkํ๊ณ , ์์์ด ์์๋ฅผ Fork ํ ๋ ๋ถ๋ชจ Task๋ฅผ ์ทจ์ํ๋ฉด ์ ๋๋ก ์์Task๊น์ง ์ทจ์๋ฅผ ์๋ ค์ฃผ๋ ํธ๋ฆฌํ ๊ธฐ๋ฅ์
๋๋ค. ๋ง์ฝ ์์Task์ ์๋ฃ๋ฅผ ์๋์ ์ผ๋ก ๊ธฐ๋ค๋ฆฌ๊ณ ์ถ์ง ์๋ค๋ฉด spawn
์ ์ฌ์ฉํ์ฌ Task๋ฅผ ๊ธฐ๋ํด์ฃผ์ธ์.
handleRequestUser
Task๋ฅผ ๊ธฐ๋์ํค๋ฉด ๊ธ๋ฐฉ REQUEST_USER
Action์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ์ํด take
Effect๊ฐ ๋ถ๋ฌ์์ง๋๋ค. ์ด "๊ธฐ๋ค๋ฆผ"์ด๋ผ๋ ํ๋์ด ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ ๋๊ธฐ์ ์ผ๋ก ์ด๋ค ๋ผ๋ ํน์ง์ ์ธ Task์ ํํ์ผ๋ก ์ด์ด์ง๋๋ค. redux-saga์ Task๋ฅผ Generatorํจ์๋ก ์ฐ๋ ์ด์ ๋ yield
์ ๋ฐ๋ผ ์ฒ๋ฆฌ์ ํ๋ฆ์ ์ผ์์ ์งํ๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ฌํ ์ฒด๊ณ ๋๋ถ์ ์ฑ๊ธ ์ค๋ ๋์ Javascript๋ก ๋ณต์์ Task๋ฅผ ๋ง๋ค์ด, ๊ฐ๊ฐ ํน์ ํ Action์ ๊ธฐ๋ค๋ฆฌ๊ฑฐ๋, ํต์ ์ฒ๋ฆฌ์ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ ค๋ ์ฒ๋ฆฌ๊ฐ ๋ฐ๋ฆฌ์ง ์๊ฒ ๋ฉ๋๋ค.
REQUEST_USER
Action์ด dispatch๋๋ฉด take
Effect๋ฅผ yield
ํ์ฌ ์ผ์์ ์ง๋ ์ฝ๋๊ฐ ์ฌ๊ฐ๋๊ณ , dispatch๋ Action ์ค๋ธ์ ํธ๋ฅผ ๋๋ ค์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ณง API ๋ถ๋ฌ๋
๋๋ค. ์ฌ๊ธฐ์ call
Effect๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๊ฒ๋ ๋ค๋ฅธ Effect์ ๊ฐ์ด ๊ทธ ์ฅ์์์ ์คํ๋์ง ์๋๊ฑด ๊ณตํต์ ์ด์ง๋ง, ์ง์ ๋ ํจ์๊ฐ Promise๋ฅผ ๋๋ ค์ค ๊ฒฝ์ฐ, ๊ทธ Promise ๊ฐ resolve๋๊ณ ๋์ ์ ์ด๋ฅผ ๋๋ ค์ค๋๋ค. take
Effect์ ๋ฎ์ ์์ง์์ด๋ค์. ํต์ ์ฒ๋ฆฌ๊ฐ ์๋ คํ๋ฉด ๋ค์ ํ๋ฒ handleRequestUser
Task๋ก ์ ์ด๋ฅผ ๋๋ ค์ฃผ๊ณ , ๊ฒฐ๊ณผ์ ๋ฐ๋ผ Action์ dispatchํฉ๋๋ค. Action์ dispatch์๋ put
Effect๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ด๊ฒ์ผ๋ก ํต์ ์ฒ๋ฆฌ ์์ฒด๋ ์๋ฃ๋์์ง๋ง, ํ๊ฐ์ง ๋ Task๋ฅผ ์ ์ํ ๋ ์์ฃผ ์ฐ๋ ์ฉ์ด์ ๋ํด ์ค๋ช
ํด๋๊ฒ ์ต๋๋ค. ์ต์ด๋ก ์ฝ๋๋ฅผ ๋ดค์๋ "์ด?" ๋ผ๊ณ ๋๋ผ์
จ์ ๊ฑฐ๋ผ ์๊ฐํ์ง๋ง, handleRequestUser
Task๋ ์ ์ฒด๊ฐ while๋ฌธ์ผ๋ก๋ ๋ฌดํ ๋ฃจํ๋ก ๊ฐ์ธ์ฌ ์์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ put
Effect๋ก Effect๋ก Action์ dispatchํ ํ, ๋ฃจํ์ ์ฒ์์ผ๋ก ๋์๊ฐ์ ๋ค์ ํ๋ฒ take
Effect๋ก REQUEST_USER
Action์ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋ฉ๋๋ค. ์ฆ, Action์ ๊ธฐ๋ค๋ ค ํต์ ์ฒ๋ฆฌ๋ฅผ ํ ๋ฟ์ธ Task๊ฐ ๋ฉ๋๋ค. ์ฌ๊ธฐ๊ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด๋ ๊ฒ ๊ทน๋จ์ ์ผ๋ก ํด์ผ ํ ์ผ์ ์ ํํด๋๋ ๊ฒ์ผ๋ก ์ฝ๋๋ ๋งค์ฐ ๋จ์ํด์ง๋๋ค. ๋น์ฐํ ๋ฒ๊ทธ๋ ์ค๊ฒ ์ฃ . ๊ฒ๋ค๊ฐ ๋น๋๊ธฐ์ฒ๋ฆฌ์ ํญ์ ๋ฐ๋ผ์ค๋ ์ฝ๋ฐฑ ์ง์ฅ, ๊น์ ๊ตฌ์กฐ, ๋ฌ๊ธ์์ด ๋ํ๋๋ Promise๊ฐ ์ฌ๋ผ์ง๊ฒ ๋ฉ๋๋ค.
redux-thunk์ redux-saga์ ๊ฐ๊ฐ์ ์ฝ๋์ ๋ํด์ ์ธ๋ฐํ๊ฒ ์ดํด ๋ณด์์ต๋๋ค. ์ฌ๊ธฐ์ ์ ๊น ๋ค๋ฅธ ๊ด์ ์ผ๋ก๋ถํฐ ์๊ฐํด๋ณด๊ณ ์ถ์ต๋๋ค. ์ด ์น์ ์ ๋จธ๋ฆฟ๊ธ์ ๋งํ "์ด๋์ ์ธ ๊ฒ ์ธ๊ฐ", "์ด๋์์ ๋ถ๋ฌ์ฌ ๊ฒ์ธ๊ฐ"์ ๋ํ ์๊ธฐ์ ๋๋ค.
redux-thunk๋ Action Creator๊ฐ ํจ์๋ฅผ ๋๊ฒจ์ฃผ๊ธฐ์ ํ์ฐ์ ์ผ๋ก Action Creator์ ๋น๋๊ธฐ์ฒ๋ฆฌ์ ์ฝ๋๋ ๊ด๋ จ๋ ๋ก์ง์ด (์ฝ๊ฐ ๋ฌด๋ฆฌํ๊ฒ) ๋ค์ด๊ฐ๋๋ค. ๋ฐ๋ฉด, redux-saga๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ๊ธฐ์ ํ๋ ์ ์ฉ์ ๋ฐฉ์์ธ Task๋ก ์ฐ์ฌ์์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ, Action Creator๋ ๋ณธ๋์ ๋ชจ์ต์ ๋์ฐพ์, Action ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ์ฌ ๋๋ ค์ฃผ๋ ์์ํ ์ํ๋ก ๋์๊ฐ๋๋ค. ๊ฐ์ธ์ ์ผ๋ก ์ด ๋ณํ๋ ์์ง ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋ ๊ฒ, redux-thunk๋ dispatch๋ ํจ์๋ฅผ ๋ถ์ก์ ์คํํ๋ ์ฑ์ง์, Middleware ์คํ(์ํ๊ฐ์ ๊ตฌ์กฐ๋๊น ์ ธ?)์ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ ๋ฐฐ์น๋ ํ์๊ฐ ์์ต๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด, ๋ค๋ฅธ Middlewareํจ์์ ๋ถ์กํ ์๋ฌ๊ฐ ๋ ์ง๋ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ฌํ ์ฌ์ ์ผ๋ก์ธํด ํจ์๊ฐ dispatch๋์๋์ง์ ๋ํด redux-thunk์ด์ธ์ ๋๊ตฌ๋ ๋ชจ๋ฅด๋ ์ฌํ์ ๋น ์ง๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ redux-thunk์ ์ง์ ์ redux-logger๋ผ๋ ๊ฐ ๋ค๋ฅธ Middleware๊ฐ ์ค๊ฒ ๋๋ฉด ์ด๋ค์ด ๋ฐ๋๊ฑด ํจ์๊ฐ ๋ฉ๋๋ค. ๋ด์ฉ์ด ์ด๋ป๊ฒ ๋ ๊ฑด์ง ์คํํ๊ธฐ ์ ๊น์ง ์ ์ ์์ต๋๋ค. ์ ๋ง ์ข์ง์๋ค์. redux-saga๋ฅผ ์ด๋ค๋ฉด Action Creator๊ฐ ํ์ค์ ์ธ Action ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ ๋ฟ์ด๊ธฐ์ redux-logger๋ก ํ์์ํฌ ์ ์์ต๋๋ค.
๊ทธ๋ผ, ์์ ํต์ ์ฒ๋ฆฌ๋ ๋งค์ฐ ๋จ์ํ ๊ฒ์ด์์ผ๋ฏ๋ก, ์ฒ์๋ณด๋ ๋ฐฉ์์ผ๋ก ์ฐ๊ธฐ๊ฐ ๊ฐ์ ๋๊ฒ ๋ฒ๊ฑฐ์ ๊ทธ๋ค์ง ๊ฐ์ฌํ๋ค๋ผ๋ ์ธ์์ด ์ ์์ ์ง๋ ๋ชจ๋ฆ ๋๋ค. ๊ทธ๋ฐ ์ด์ ๋ก, ํ์ค์ ํ๋ก์ ํธ์์๋ ํ์ํ ๋ฒํ ๊ธฐ๋ฅ์ถ๊ฐ๋ฅผ ํด๋ด ์๋ค. ๋ณต์กํ ์๋ก ์ง๊ฐ๋ฅผ ๋ฐํํ๋๊ฒ redux-saga์ ๋๋ค.
๊ทธ๋ค์ง ์ด๋ ค์ด ์ฒ๋ฆฌ๋ผ๋ฉด ์ดํดํ๊ธฐ ์ด๋ ค์์ง๊ธฐ ๋๋ฌธ์, ์ด์ Redux์ middleware๋ฅผ ์ ๊ทน์ ์ผ๋ก ์จ๋ณด๊ธฐ๋ผ๋ ํฌ์คํ
์ ์์ฉ ์๋ก ๋ง๋ API ์์ฒญ์ ์ฒด์ธ์ํค๊ธฐ๋ฅผ redux-thunk์ redux-saga๋ก ๊ฐ๊ฐ ์จ๋ณด๊ฒ ์ต๋๋ค. ํฌ์คํ
์ ์์ ์ ์ด๋ค ํต์ ์ฒ๋ฆฌ๊ฐ ๋๋ ์ดํ, ๊ทธ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋ค์ ํต์ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ํ๋ ๊ฒ์
๋๋ค. ์ด๋ฒ ์์ ์์๋ ์ ์ ์ ๋ณด๋ฅผ ์ทจ๋ํ ์ดํ, ์ ์ ์ ๋ณด์ ํฌํจ๋ ์ง์ญ๋ช
์ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ง์ญ์ ์ด๊ณ ์๋ ๋ค๋ฅธ ์ ์ ๋ฅผ ๊ฒ์ํ์ฌ ์ ์ํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ด
๋๋ค. ์๋ก์ด api.js
์ ์ถ๊ฐ๋ searchByLocation
ํจ์๋ redux-thunk์ redux-saga๋ก ๋ง๋ ์์ ๋ชจ๋ ์ฌ์ฉํฉ๋๋ค. Action Type์ด๋ Action Creator๋ฑ์ ์ ๋นํ ์ ์ํด๋๋ค๊ณ ์๊ฐํด์ฃผ์ธ์.
์ญ์์ฃผ: ์์ ์ ์๋ณธ์ด ๋๋ ๋งํฌ๋ ์ผ๋ณธ์ด์ด์ง๋ง ์ด๋ฏธ ํ์ํ ๋ด์ฉ๋ค์ ๋ณธ ํฌ์คํ ์ ๋ค ์๊ธฐ์ ๋ชจ๋ฅด์ ๋ ํฌ๊ฒ ๋ฌธ์ ์์ต๋๋ค.
api.js
export function searchByLocation(name) {
return fetch(`http://localhost:3000/users/${id}/following`)
.then(res => res.json())
.then(payload => { payload })
.catch(error => { error });
}
actions.js
export function fetchUser(id) {
return dispatch => {
// ์ ์ ์ ๋ณด๋ฅผ ์ฝ์ด๋ค์ธ๋ค
dispatch(requestUser(id));
API.user(id).then(res => {
const { payload, error } = res;
if (payload && !error) {
dispatch(successUser(payload));
// ์ฒด์ธ: ์ง์ญ๋ช
์ผ๋ก ์ ์ ๋ฅผ ๊ฒ์
dispatch(requestSearchByLocation(id));
API.searchByLocation(id).then(res => {
const { payload, error } = res;
if (payload && !error) {
dispatch(successSearchByLocation(payload));
} else {
dispatch(failureSearchByLocation(error));
}
});
} else {
dispatch(failureUser(error));
}
});
};
}
...ํ . ๋ญ ํ๊ณ ํ์ง๋ ์๋๋ค. ์๋ง ๋ณดํต ์ด๋ ๊ฒ ๋๊ฒ ์ฃ . ํ์ง๋ง ...
ํ(๋ญ๊ฐ ์ฐ์ฐํ) ๋๋์ด ๋๋ค์. ๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ ๋ ๋ค๋ฅธ ์ฒด์ธ์ ๋๋ฆฌ๊ฑฐ๋ ์ฒด์ธ์ํค๋ ์์น๋ฅผ ๋ฐ๊พธ๊ฒ ๋๋ค๋ฉด ๊ณค๋ํด ์ง๋ฏ ํ๋ค์. ๋ฌด์๋ณด๋ค๋ ๊ธฐ๋ถ ๋์๊ฑด fetchUser
๋ผ๋ Action Creator๋ฅผ ๋ถ๋ฌ๋ด์ ์ ์ ์ ๊ฒ์๊น์ง ์คํํ๊ณ ์๋๋ ๋ผ๋ ์ ์
๋๋ค. Middleware๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌ๋ฅผ ๋ถ๋ฆฌํ๋ฉด ์ด ๋ฌธ์ ์ ์ด ์กฐ๊ธ์ ํด์๋ ๋ฏ ํ์ง๋ง, ์ดํ๋ฆฌ์ผ์ด์
๋
๋ฆฝ์ ์ธ DSL๊ณผ ๊ฐ์ ์ฝ๋๊ฐ ๊ณ์ ๋์ด๋๊ฒ ๋๋ ๊ฒ ์ญ์ ๊ดด๋ก์ธ ๋ฏ ํฉ๋๋ค.
์ญ์, ์ด ํฌ์คํ ์ redux-saga๋ฅผ ํธ์ ํ๊ณ ์์ต๋๋ค. ํ์ง๋ง redux-thunk๋ฅผ ์ฒ ์ ํ๊ฒ ๊น๋ด๋ฆด ์๋๋ ์์ต๋๋ค. ์ค์ ์ ๋ ์์ง ์ฌ์ฉํ๋ ๋ถ๋ถ์ด ์์ต๋๋ค. ์ด์ฉ ์ ์์ด redux-thunk๋ฅผ ๊ณ์ ์ฌ์ฉํ ์ ๋ฐ์ ์์ผ์ ๋ถ๋ ์์๊ฑฐ๋ผ ์๊ฐ๋๋ฏ๋ก, ํน์ "redux-thunk๋ผ๋ ์ด๋ ๊ฒ ์ฐ๋ฉด ๊ดด๋ก์์ ์ค์ผ์ ์์ด!" ๊ฐ์ ์๊ฒฌ์ด ์์ผ์๋ฉด ์ฝ๋ฉํธ๋ก ์๋ ค์ฃผ์ธ์.
๊ทธ๋ผ redux-saga๋ก ์จ๋ด ์๋ค.
sagas.js
// ์ถ๊ฐ
function* handleRequestSearchByLocation() {
while (true) {
const action = yield take(SUCCESS_USER);
const { payload, error } = yield call(API.searchByLocation, action.payload.location);
if (payload && !error) {
yield put(successSearchByLocation(payload));
} else {
yield put(failureSearchByLocation(error));
}
}
}
// ๋ณ๊ฒฝ์์!
function* handleRequestUser() {
while (true) {
const action = yield take(REQUEST_USER);
const { payload, error } = yield call(API.user, action.payload);
if (payload && !error) {
yield put(successUser(payload));
} else {
yield put(failureUser(error));
}
}
}
export default function* rootSaga() {
yield fork(handleRequestUser);
yield fork(handleRequestSearchByLocation); // ์ถ๊ฐ
}
๋ณด์๋ค์ถ์ด handleRequestUser
Task๋ ๋ณ๊ฒฝ์ ์ด ์์ต๋๋ค. ์๋กญ๊ฒ ์ถ๊ฐ๋ handleRequestSearchByLocation
Task๋ handleRequestUser
Task์ ๊ฑฐ์ ๋์ผํ ์ฒ๋ฆฌ์
๋๋ค. rootSaga
Task๋ handleRequestSearchByLocation
Task๋ฅผ ๊ธฐ๋ํ๊ธฐ์ํด fork
๋ฅผ ํ๋ ๋ ์ถ๊ฐํ๊ณ ์์ต๋๋ค. ์กฐ๊ธ ๊ธธ์ด์ง์ง๋ง, ์ฒ๋ฆฌ์ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- redux-saga์ Middleware๊ฐ
rootSaga
Task๋ฅผ ๊ธฐ๋์ํจ๋ค. fork
Effect์ ๋ฐ๋ผhandleRequestUser
์handleRequestSearchByLocation
Task๊ฐ ๊ธฐ๋๋๋ค.- ๊ฐ๊ฐ์ Task์ ๋ํด
take
Effect๋ก๋ถํฐREQUEST_USER
์SUCCESS_USER
Action์ด dispatch๋๋ ๊ฒ์ ๊ธฐ๋ค๋ฆฐ๋ค. - (๋๊ตฐ๊ฐ๊ฐ
REQUEST_USER
Action์ dispatchํ๋ค.) call
Effect์ผ๋กAPI.user
ํจ์๋ฅผ ๋ถ๋ฌ์์, ํต์ ์ฒ๋ฆฌ๊ฐ ๋๋๊ธธ ๊ธฐ๋ค๋ฆฐ๋ค.- (ํต์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ค.)
put
Effect๋ฅผ ์ฌ์ฉํ์ฌSUCCESS_USER
Action์ dispatchํ๋ค.handleRequestSearchByLocation
ํ์คํฌ๊ฐ ๋ค์ ์์๋์ด,call
Effect๋กAPI.searchByLocation
ํจ์๋ฅผ ๋ถ๋ฌ์์, ํต์ ์ฒ๋ฆฌ๊ฐ ๋๋๊ธธ ๊ธฐ๋ค๋ฆฐ๋ค.- (ํต์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ค.)
put
Effect๋ฅผ ์ฌ์ฉํ์ฌSUCCESS_SEARCH_BY_LOCATION
Action์ dispatchํ๋ค.- ๊ฐ๊ฐ์ Task์์ while ๋ฃจํ๊ฐ ์ฒ์์ผ๋ก ๋์๊ฐ
take
๋ก Action์ dispatch๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.
๊ฐ๊ฐ์ ํ์คํฌ๋ฅผ ์ฃผ์ํด์ ๋ณด๋ฉด ๋จ์ํ ๊ฒ ๋ฐ์ ํ์ง ์๊ธฐ ๋๋ฌธ์ ์ดํดํ๊ธฐ ์ฌ์์ง์ง ์์๋์? ๊ฒ๋ค๊ฐ ์ด ์ฝ๋๋ฅผ ํ์ฅํ์ฌ ์ฒด์ธ์ ๋๋ฆฌ๊ฑฐ๋, ์ฒด์ธ์ ์์๋ฅผ ๋ฐ๊พธ๊ฑฐ๋ ๋ฌด์์ ํ๋๋ผ๋ Task๋ ํ๋๋ง ์ง์คํ๊ณ ์์ผ๋ฏ๋ก ๋ค๋ฅธ ๋ฌด์์ ํด๋ ๊ทธ๋ค์ง ์ํฅ์ ๋ฐ์ง ์์ต๋๋ค. ์ด๋ฌํ ์ฑ์ง์ ์ ๊ทน์ ์ผ๋ก ์ด์ฉํ์ฌ Task๊ฐ ๋๋ฌด ๋น๋ํด์ง๊ธฐ ์ ์ ๊ณ์ํด์ ์๋ผ ๋๋๋ ๊ฒ์ผ๋ก ์ฝ๋์ ๊ฑด์ ์ฑ์ ์ ์งํ ์ ์์ต๋๋ค.
redux-saga๋ฅผ ์ ๊ทน์ ์ผ๋ก ์ฐ๊ณ ์ถ์ ์ด์ ๋ก์ ํ
์คํธ์ ํธ๋ฆฌํจ์ ๋ค์์ต๋๋ค. ์์ง ๋ค๋ฅธ์ฌ๋์๊ฒ ๋ฒ์ญํ ๋งํผ ๋
ธํ์ฐ์ ์ถ์ ์ด ์๋์์ง๋ง, ๋ถ์๊ธฐ๋ฅผ ํ์
ํ๋ ์ ๋๋ก ์จ๋ณด๊ฒ ์ต๋๋ค. ํ
์คํธ ๋์์ ์ต์ด์ ํต์ ์ฒ๋ฆฌ์ ์ฝ๋๋ก ํฉ๋๋ค. ๋ณต์กํ๊ฒ ๋๊ธฐ ์ ์ ์์ ์
๋๋ค. ๋จผ์ ๋จ์ํด๋ณด์ด๋ rootSaga
Task์ ํ
์คํธ๋ถํฐ ์จ๋ณด๊ฒ ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ , ํ
์คํธ์ฝ๋๋ mocha + power-assert ์
๋๋ค.
sagas.js
export default function* rootSaga() {
yield fork(handleRequestUser);
}
์ฌ๊ธฐ์ ๋ํ ํ ์คํธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
test.js
describe('rootSaga', () => {
it('launches handleRequestUser task', () => {
const saga = rootSaga();
ret = saga.next();
assert.deepEqual(ret.value, fork(handleRequestUser));
ret = saga.next();
assert(ret.done);
});
});
Task๋ฅผ ํฌํฌํ๊ณ ์๋์ง ํ
์คํธ๋ฅผ ํ๋ค. ๋ผ๊ณ ํ๋ฉด ์ด๋ ต๊ฒ ๋ค๋ฆฌ์ง๋ง, ์ฌ๊ธฐ์ Task๋ผ๋ ๊ฑด ๋จ์ํ Generator ํจ์๋ก, Task๊ฐ ๋๋ ค์ฃผ๋๊ฑด ๋ชจ๋ ๋จ์ํ ์ค๋ธ์ ํธ๋ผ๋๊ฑธ ๋ ์ฌ๋ฆฝ์๋ค. ๊ณ ๋ก redux-saga์ ๋์ด์ง Task์ ํ
์คํธ๋ ๋จ์ํ ์ค๋ธ์ ํธ๋ฅผ ๋น๊ตํ๋ ๊ฒ์ผ๋ก ๋๋ถ๋ถ ์ถฉ๋ถํฉ๋๋ค. ์ด rootSaga
Task๊ฐ ํฌํฌํ๊ณ ์๋์ง๋ฅผ ํ์ธํ๊ธฐ ์ํด fork
Effect๋ก ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ์ฌ ๋น๊ตํ๋ ๊ฒ์ผ๋ก OK์
๋๋ค. ์ด expected๋ก ์ง์ ๋ ์ค๋ธ์ ํธ๋ Task์ ์์ฑ์ ์ฐ์ฌ์ง Effect Creator๋ก ์์ฑํ์ฌ ๋ฌธ์ ์๋๊ฒ์ด ์ฌ๋ฐ๋ ํฌ์ธํธ์
๋๋ค. ํ
์คํธํด์ผ ํ ๊ฒ์ ์ด Task๊ฐ ๋ฌด์์ ํ๊ณ ์๋ ๊ฐ๋ก์จ, ๊ทธ์ ์์ ๋ฌด์์ ํ๋๊ฐ๋ ์ ํ์ ์์ต๋๋ค.
์ด๊ฒ๋ง์ด๋ฉด ํ
์คํธํ ๋๋์ด ์๋๋ฏ๋ก ์ข ๋ ๋ณต์กํ handleRequestUser
Task๋ ํ
์คํธ๋ก ์จ๋ด
๋๋ค.
sagas.js
function* handleRequestUser() {
while (true) {
const action = yield take(REQUEST_USER);
const { payload, error } = yield call(API.user, action.payload);
if (payload && !error) {
yield put(successUser(payload));
} else {
yield put(failureUser(error));
}
}
}
ํต์ ์ฒ๋ฆฌ๊ฐ ์ฑ๊ณตํ๋์ง ์คํจํ๋์ง๋ก ๋ถ๊ธฐ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ํ ์คํธ๋ ๊ฐ๊ฐ์ ์ผ์ด์ค๋ฅผ ์ ์ต๋๋ค.
test.js
describe('handleRequestUser', () => {
let saga;
beforeEach(() => {
saga = handleRequestUser();
});
it('receives fetch request and succeeds to get data', () => {
let ret = saga.next();
assert.deepEqual(ret.value, take(REQUEST_USER)); // (A')
ret = saga.next({ payload: 123 }); // (A)
assert.deepEqual(ret.value, call(API.user, 123)); // (B')
ret = saga.next({ payload: 'GOOD' }); // (B)
assert.deepEqual(ret.value, put(successUser('GOOD')));
ret = saga.next();
assert.deepEqual(ret.value, take(REQUEST_USER));
});
it('receives fetch request and fails to get data', () => {
let ret = saga.next();
assert.deepEqual(ret.value, take(REQUEST_USER));
ret = saga.next({ payload: 456 });
assert.deepEqual(ret.value, call(API.user, 456));
ret = saga.next({ error: 'WRONG' });
assert.deepEqual(ret.value, put(failureUser('WRONG')));
ret = saga.next();
assert.deepEqual(ret.value, take(REQUEST_USER));
});
});
์ด๊ฒ์ Generatorํจ์์ ํ
์คํธ๊ฐ ๋๋ฏ๋ก ์ต์ํ์ง ์์ผ๋ฉด ์ด๋ ต๊ฒ ๋ค์. ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ next()
๋ฅผ ๋ถ๋ฅผ๋ ์ฒ์์ yield
๊น์ง ์คํ๋๋ ๊ทธ๋์ ์ฐ๋ณ์ ๊ฐ์ ๋ฉํํ ๊ฒ์ด ๋ฆฌํด๊ฐ์ผ๋ก ๋์ต๋๋ค. ์ฐ๋ณ ๊ฐ ์์ฒด๋ value
ํ๋กํผํฐ์ ๊ฒฉ๋ฉ๋์ด์์ผ๋ฏ๋ก ๊ฑฐ๊ธฐ์ ํ์ธํฉ๋๋ค.
์ง๊ธ, Task๋ ์ ์ง๋์ด์์ต๋๋ค. ์ด๋ฅผ ์ฌ๊ฐํ๊ธฐ ์ํด์๋ ๋์ฑ์ด next()
๋ฅผ ๋ถ๋ฌ๋
๋๋ค. ์ด next()
์ ์ธ์๋ก์จ ๋๊ฒจ์ค ๊ฒ์, Task๊ฐ ์ฌ๊ฐ๋ ๋์ yield
๋ก๋ถํฐ ๋์จ ๋ฆฌํด ๊ฐ์ด ๋ฉ๋๋ค. ์ฆ ์ฝ๋์ค์ (A)
๋ก ๋๊ฒจ์ง๋ ๊ฒ์ด (A')
๋ก๋ถํฐ ๋์ฌ๊ฑฐ๋ผ ๊ธฐ๋๋๋ ๋ฆฌํด ๊ฐ์ด๋ผ๋ ๊ฒ์ด์ฃ . ๊ฐ์ ๋ฐฉ์์ผ๋ก (B)
๋ก ๋๊ฒจ์ง ํต์ ๊ฒฐ๊ณผ์ ์ค๋ธ์ ํธ๊ฐ (B')
์ call
Effect๋ก ๋ถ๋ฌ์ง ๊ฒฐ๊ณผ๊ฐ ๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก, ํต์ ์ฒ๋ฆฌ๊ฐ ๋๋๋ฉด, ๋ค์ ๋ฆฌํ์คํธ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์ํ๊ฐ ๋์๋์ง ํ์ธํฉ๋๋ค. Task๋ฅผ ๋๊ธฐ์ ์ผ๋ก ์ป๊ธฐ์, ํ ์คํธ ์ฝ๋๋ ๋๊ธฐ์ ์ผ๋ก ๋์์ต๋๋ค.
์กฐ๊ธ ์ค๋ช ์ด ๋น ๋ฅธ ๋๋๋ ์์ง๋ง, ์ redux-saga์ ์คํ๋ชจ๋ธ๋ก ์ ๊ณต๋ ์ปค๋งจ๋๋ฅผ ์ฌ์ฉํ์ฌ Task๋ฅผ ๋ง๋๋ ๊ฒ์ธ๊ฐ๊ฐ ์ดํด๋์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๋ชจ๋ ๊ฒ์ด ์์ธก ๊ฐ๋ฅํ ํ ์คํธ๊ฐ ๊ฐ๋จํ๊ฒ ๋์ด, ๋ณต์กํ ๋ชฉ์ ๋ง๋ค ํ์์ฑ๋ ์ต์ํ์ผ๋ก ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
Task์ ์ค๋ช ์ผ๋ก ์ด๊ฒ ์ ๊ฒ ๋์ด๊ฐ๋ฒ๋ ธ๊ธฐ์, ์กฐ๊ธ๋ redux-saga์ ์ค์ ์ ๋ํด ์ฃผ์์ ๋ ์๋ ค๋๋ฆฌ๊ฒ ์ต๋๋ค. ์์ ๋งํ์ง๋ง, ๊ธฐ๋ณธ์ ์ธ๊ฑด ๊ณต์ ๋ฌธ์๋ฅผ ์ฝ๋ ๊ฒ์ด ๊ฐ์ฅ ์ข์ต๋๋ค. ์ดํ ๋ํญ ๋ฐ๋ ๊ฐ๋ฅ์ฑ์ ์ ์ง๋ง, ๊ทธ๋ด ๋ ๊ธฐ๋ ์ ์๋๊ฑด ์ญ์ ๊ณต์์ด๋๊น์.
์์ ์ฝ๋์ ๋๋ ํ ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๊ธ์ ์์์ฐจ๋ฆฌ์
จ์์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง, redux-saga๋ฅผ ์ธ ๋ 2๊ฐ์ง๋ฅผ ํฉ๋๋ค. ํ๋๋ Store์ Middleware๋ฅผ ์ง์ด๋ฃ๊ณ , ๋ค๋ฅธ ํ๋๋ Task๋ฅผ ์ ์ํฉ๋๋ค. ์ดํ๋ ์ ํ์ ์ธ ์
์
์ฝ๋์
๋๋ค. redux-logger
๋ ํ์ํ์ง ์์ผ๋ฉด ์ง์์ฃผ์ธ์.
store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import logger from 'redux-logger';
import reducer from './reducers';
import rootSaga from './sagas';
export default function configureStore(initialState) {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
initialState,
applyMiddleware(
sagaMiddleware, logger()
)
);
sagaMiddleware.run(rootSaga);
return store;
};
์ด์ ์ ๊ฒช์๋ ์ผ๋ก, ์๋ํ์ง ์์ ํ์ด์ง์์ redux-saga๊ฐ ๊ธฐ๋๋์ด ํต์ ์ฒ๋ฆฌ๊ฐ ์์๋ ์ ์ด ์์์ต๋๋ค. ์์ธ์ store.js
์ ์์์ต๋๋ค.
store.js
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(
sagaMiddleware, logger()
)
);
sagaMiddleware.run(rootSaga);
export default store;
configureStore
ํจ์๋ฅผ exportํ๋ ๋์ ์ ์์ฑํ Store๋ฅผ exportํ๊ณ ์๋ค์. ๊ทธ๋ฆฌ๊ณ saga.js
๋ ์ด๋ฐ ์ํ์์ต๋๋ค.
sagas.js
export default function* rootSaga() {
yield fork(loadHogeHoge);
}
์ด๊ธฐํ์ ๋ฌด์ธ๊ฐ๋ฅผ ์ฝ์ด๋ค์ด๋ ํํ์ ํ์คํฌ์ ๋๋ค.
index.js
import store from './store.js';
// ...
const el = document.getElementById('container');
if (el) {
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
);
}
์ด๋ฏธ ํ์ ํ์ จ์๊ฑฐ๋ผ ์๊ฐ๋์ง๋ง, ์์ ๊ตฌ์ฑ์ด๋ฉด Provider ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋์๋์ง ๋ง์๋์ง์ ์๊ด์์ด Store๊ฐ ์ด๊ธฐํ๋์ด Middleware๋ ์ด๊ธฐํ๋๊ณ ๋ง๋๋ค. ๊ทธ ๊ฒฐ๊ณผ, ๊ธฐ๋์ ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ด๋ ํํ์ Task๋ผ๋ฉด ์๋ฑํ ํ์ด๋ฐ์ ์ฌํ๊ฐ ๋ฒ์ด์ง๋ ๊ฒ์ ๋๋ค. ์กฐ์ฌํ๋๋ก ํฉ์๋ค. (์ ๋ ๋ฐ์ฑ์..)
v0.10.0 ๋ถํฐ redux-saga์ ๊ธฐ๋๋ฐฉ๋ฒ์ด ๋ฐ๋์์ต๋๋ค.
before.js
const store = createStore(
reducer,
applyMiddleware(createSagaMiddleware(rootSaga))
)
์ด๋ ๊ฒ ์ฐ๋ ๊ฒ์ด
after.js
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
initialState,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga);
์ด๋ ๊ฒ ๋์์ต๋๋ค. ์ด๊ธฐ์คํ Task๋ฅผ Middleware์ ์์ฑ์๊ฐ ์๋๋ผ, Store์ ์ด๊ธฐํ๊ฐ ์๋ฃ๋ ๋ค์ run
๋ฉ์๋๋ฅผ ๋ถ๋ฅด๋ ๊ฒ์ผ๋ก ๊ธฐ๋ํฉ๋๋ค.
Task๋ ํ๋ํ๋ ๋
๋ฆฝํ๊ฒ ์คํ๋๋ฏ๋ก, ํ ๊ฒ์ ์ ํํด์ ๋จ์ํ๊ฒ ์ ์งํ๋ฉด ๋๋ฒ๊ทธ ํด์ด ํ์ํ ๋งํผ ๋ณต์กํด์ง์ง ์๊ฒ ์ง๋ง, ์ผ๋จ, redux-saga์๋ ๋ชจ๋ํฐ๋ง ํด์ ์ง์ด ๋ฃ์ ์ ์๋ ์ธํฐํ์ด์ค๊ฐ ์ค๋น๋์ด ์์ต๋๋ค. effectTriggered
, effectResolved
, effectRejected
, effectCancelled
์ 4๊ฐ์ ํ๋กํผํฐ๋ฅผ ๊ฐ์ง๋ ์ค๋ธ์ ํธ๋ฅผ createSagaMiddleware ํจ์์ ์ค๋ธ์ ํธ๋ก์จ ๋๊ฒจ์ค๋๋ค.
store.js
import sagaMonitor from './saga-monitor';
export default function configureStore(initialState) {
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
const store = createStore(...
๋ชจ๋ํฐ์ ์ ์ฉ์ ์ผ๋จ redux-saga์ examples/sagaMonitor๋ฅผ ์จ๋ด์ฃผ์ธ์. ๊ทธ๋ฆฌ๊ณ , ์ด ๋ชจ๋ํฐ๋ ๋ํดํธ๋ก ์๋ฌด๊ฒ๋ ํ์ํ์ง ์์ผ๋ฏ๋ก, ์ฝ๋์ค์ VERBOSE
๋ผ๋ ๋ณ์๋ฅผ true
๋ก ๋ฐ๊พธ์
์ผ ๋ ๋ค๊ธฐ ์์ํฉ๋๋ค. ๋จ, redux-logger
์ ๊ฐ์ด ๋ก๊ทธ๊ฐ ๊ณ์ํด์ ํ๋ฌ๊ฐ๋ ๊ฑธ ๋ณด๋๊ฒ ์๋๋ผ, ํ์ํ ๋ ๋ธ๋ผ์ฐ์ ํด๋ก๋ถํฐ window.$$LogSagas
ํจ์๋ฅผ ๋ถ๋ฌ์ Task tree๋ฅผ ์ง์ผ๋ณด๋๊ฒ ์ฃผ ๋ชฉ์ ์
๋๋ค. ์คํํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค. ํ์ง๋ง, ๊ทธ๋ค์ง ๋ฉ์์ด ๋ณด์ด์ง ์์ผ๋ฏ๋ก D3.js๋ก ์๊ฐํ ํด์ ๋ง๋ค์ด ๋ณผ ์๊ฐ์
๋๋ค.
์ด ๋ค์์ API ์์ฒญ์์ ์ค๋กํ๋ง์์ ์๊ฐํ๋ ์์ ์๋ ๋ชจ๋ํฐ๊ฐ ํฌํจ๋์ด ์์ผ๋ฏ๋ก ๋ฐ๋ชจ๋ก ์ํํด ๋ณด์ค ์ ์์ต๋๋ค.
redux-saga์๋ ํ๋ถํ ์์ ๊ฐ ์ค๋น๋์ด ์์ต๋๋ค. ๋ญ๊ฐ ๊ณค๋ํ ๋, ํํธ๊ฐ ์๋ ์ถ์ ๋ ๋ณด๋ฉด ์ข์ต๋๋ค. ...ํ์ง๋ง, ์ด๊ฑธ๋ก ๋ง๋ฌด๋ฆฌํ๊ธฐ์ ์์ฝ๊ธฐ์ ๋ค๋ฅธ ์ด์ฉ ์๋ฅผ ์๊ฐํฉ๋๋ค. ํนํ ๋ช์ผ์ ๋ฆด๋ฆฌ์ฆ๋ 0.10.0์ ์ ๊ธฐ๋ฅ์ธ eventChannel๋ฅผ ์ฌ์ฉํ ์์ ๋ ๊ทธ๋ค์ง ์์ผ๋ฏ๋ก ์ฐธ๊ณ ๊ฐ ๋ ๋ฏ ํฉ๋๋ค.
์ญ์์ฃผ: ์ฌ๊ธฐ์ ์๊ฐ๋๋ ์ค์ ์์ ๋ค์ ์์ค ์ฝ๋์ ๋ฐ๋ชจ๋ค์ ๋ฐ์ ๋งํฌ์์ ์ฐพ์ผ์ค ์ ์์ต๋๋ค. ์ด ํฌ์คํ ์ ์ค๋ช ๋์ง ์์ ์์ ๋ ์์ผ๋ ์ด์ชฝ๋ ํ์ด ๋ด์ฃผ์๋ฉด ๋์ฑ ์ข์ ๋ฏํฉ๋๋ค.
์์ค ์ฝ๋ : https://github.com/kuy/redux-saga-examples ๋ฐ๋ชจ : http://kuy.github.io/redux-saga-examples/
ํ
์คํธ ํ๋์ ์๋ ์์ฑ ๊ธฐ๋ฅ์ ๋ฃ์ ๋, ๋จ์ํ๊ฒ ๋ง๋ ๋ค๋ฉด dispatch๋ Action์ take
๋ก ๋ฐ์๋ค์ฌ call
๋ก ์์ฒญ์ ๋ฐํํ์ฌ ๊ฒฐ๊ณผ๋ฅผ put
์ผ๋ก ํ๋ฉด ์ข์๋ณด์
๋๋ค. ๋จ, ์ด๊ฒ์ ์ผ๋ฐ์ ์ธ ํต์ ์ฒ๋ฆฌ๋ก์จ, ๊ทธ๋๋ก ์ ์ฉํด๋ฒ๋ฆฌ๋ฉด ์
๋ ฅ ํ ๋๋ง๋ค ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ด๊ธฐ์ ๊ทธ๋ค์ง ์ข์ง ๋ชปํฉ๋๋ค. ์ด ์์ ์์๋ ์ด๊ธฐ์ ๋ชป์๊ธด ์๋ ์์ฑ ๊ธฐ๋ฅ์ ๋ฉ์ง๊ฒ ๊ณ ์ณ๋๊ฐ๋ ๊ณผ์ ์ ๋ณด์ฌ๋๋ฆฌ๊ฒ ์ต๋๋ค.
๋ฐ๋ชจ: Autocomplete ์์ ์ฝ๋: kuy/redux-saga-examples > autocomplete
">https://github.com/kuy/redux-saga-examples/tree/master/autocomplete)-->sagas.js
function* handleRequestSuggests() {
while (true) {
const { payload } = yield take(REQUEST_SUGGEST);
const { data, error } = yield call(API.suggest, payload);
if (data && !error) {
yield put(successSuggest({ data }));
} else {
yield put(failureSuggest({ error }));
}
}
}
export default function* rootSaga() {
yield fork(handleRequestSuggests);
}
ํต์ ์ฒ๋ฆฌ์ ์ฝ๋ ๊ทธ๋๋ก์ด๋ค์. ์ฐธ๊ณ ๋ก ์ค์ ์ด ์ฝ๋, ํฐ ๋ฌธ์ ๋ฅผ ์ง๋๊ณ ์์ต๋๋ค. ๋ฌด์์ธ๊ฐ ํ๋ฉด, ํต์ ์ฒ๋ฆฌ์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์ dispatch๋๋ Action์ ํ๋ ค๋ณด๋ด ๋ฒ๋ฆฝ๋๋ค. ์์ ์์๋ ํต์ ์ฒ๋ฆฌ๋ถ๋ถ์ ๋๋ฏธ๋ก์จ setTimeout
์ ์ฌ์ฉํ์ฌ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ฏ์ด ๋ง๋ค์๊ธฐ ๋๋ฌธ์, ์ด ๋ถ๋ถ์ ์๊ฐ์ 3์ด ์ ๋๋ก ๋ฐ๊พธ๋ฉด ํ์คํ๊ฒ ๋ณด์ผ๊ฒ๋๋ค.
์ด๋ฐ ์ด์ ๋ก ๋ฉ์ง๊ฒ ๋ง๋ค๊ธฐ ์์ ๋ฒ๊ทธ๋ฅผ ์ก์์๋ค. ๋ฌธ์ ๋ call
๋ก API.suggest
์ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๊ณณ์
๋๋ค. ์ด๊ฒ์ด ๋ถ๋ฌ์ง๋๊ฑธ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ take
๋ก ๋์๊ฐ๋ฉด ํ๋ฆฌ์ง๋ ์๊ฒ๋ฉ๋๋ค. ๊ทธ๋ ๋ค๋ฉด fork
๋ก ์๋ก์ด Task๋ฅผ ๊ธฐ๋์ํค๋ ๊ฒ์ด ์ข์๋ณด์ด๋ค์.
sagas.js
function* runRequestSuggest(text) {
const { data, error } = yield call(API.suggest, text);
if (data && !error) {
yield put(successSuggest({ data }));
} else {
yield put(failureSuggest({ error }));
}
}
function* handleRequestSuggest() {
while (true) {
const { payload } = yield take(REQUEST_SUGGEST);
yield fork(runRequestSuggest, payload);
}
}
export default function* rootSaga() {
yield fork(handleRequestSuggest);
}
์ด๋ฐ ํํ๊ฐ ๋ฉ๋๋ค. ์ด๊ฒ์ผ๋ก handleRequestSuggest
Task๋ก ํต์ ์ฒ๋ฆฌ๊น์ง ํธ๋ค๋งํ๊ณ ์์ง๋ง, call
์ดํ์ ๋ถ๋ถ์ ๋ฐ๋ก Task๋ก ๋๋์์ต๋๋ค. ์๋ฌด๋ฆฌ ์ด๋ฒ๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ์ผ์ด๋์ง ์๋ ๋ค๊ณ ํด๋, Action์ ๊ฐ์ํ๋ Task์ ํต์ ์ฒ๋ฆฌ๋ฅผ ํ๋ ํ์คํฌ๋ฅผ ๋๋๋ ๊ฒ์ด ์ข์๋ณด์
๋๋ค. ์ด๊ฑธ๋ก ๋งํ์์ด ๋ฆฌํ์คํธ๋ ๋ ๋ฆด ์ ์๋ค์! ์ ๋๋ค์!
์, ๋ฒ๊ทธ๋ ๊ณ ์ณค์ง๋ง ๊ณต๋ถ๋ฅผ ์ํด ์ ๊น ์๊ธธ๋ก ์๊ฒ ์ต๋๋ค. redux-saga๋ก Task๋ฅผ ์ฐ๊ณ ์์ผ๋ฉด ์์ ํจํด์ด ๋น๋ฒํ๊ฒ ๋์ค๊ธฐ ๋๋ฌธ์ takeEvery
๊ฐ ์ค๋น๋์ด์์ต๋๋ค. ์ด๊ฒ์ ์ฌ์ฉํ์ฌ ๋ค์ ์จ๋ด
์๋ค.
sagas.js
import { call, put, fork, takeEvery } from 'redux-saga/effects';
function* runRequestSuggest(action) {
const { data, error } = yield call(API.suggest, action.payload);
if (data && !error) {
yield put(successSuggest({ data }));
} else {
yield put(failureSuggest({ error }));
}
}
function* handleRequestSuggest() {
yield takeEvery(REQUEST_SUGGEST, runRequestSuggest);
}
export default function* rootSaga() {
yield fork(handleRequestSuggest);
}
takeEvery
๋ ์ง์ ํ Action์ dispatch๋ฅผ ๊ธฐ๋ค๋ ค, ๊ทธ Action์ ์ธ์๋ก์จ Task๋ฅผ ๊ธฐ๋ํฉ๋๋ค. ์ด์ ์ ํฌํผ ํจ์๋ก์จ ์ ๊ณต๋์์ง๋ง, 0.14.0๋ถํฐ ์ ์์ผ๋ก Effect๊ฐ ๋์์ต๋๋ค. ๋จ, ํฌํผ์ takeEvery
๋ ์์ด์ง ์์ ์ด๋ฏ๋ก ๋ฐ๊พธ์ค๊ฑธ ์ถ์ฒํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ , Effect๋ก์จ takeEvery
์ ํฌํผ์ takeEvery
๋ ๋ค๋ฆ
๋๋ค. ๋ฐ๋ผ์ Effect์ธ takeEvery
๋ฅผ yield*
๋ก ์ฌ์ฉํด์๋ ์๋ฉ๋๋ค.
๋ฒ๊ทธ๋ ๊ณ ์ณค๊ณ , ์ด๊ฑธ๋ก ๊ณ ์น ์ค๋น๊ฐ ๋์์ต๋๋ค. ์ด๋ ํ ๋์์ด ์ข์์ง ์ ๋ฆฌํ๊ธฐ ์ํด์ ์๋๋ฆฌ์ค๋ฅผ ์จ๋ด ์๋ค.
- 1๊ธ์๋ฅผ ์ ๋ ฅํ๋ค.
- ๋ฐ๋ก ๋ฆฌํ์คํธ๋ฅผ ๋ ๋ฆฌ์ง ์๋๋ค.
- ๋ช ๊ธ์ ๋ ์ ๋ ฅํ๋ค.
- ์์ง ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ด์ง ์๋๋ค.
- ์๋ฌด๊ฒ๋ ์ ๋ ฅ์ด ์๋ ์ํ๊ฐ ์ผ์ ์๊ฐ ์ง์๋๋ฉด ๋ฆฌํ์คํธ๋ฅผ ๋ณด๋ธ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก๋ ์ผ์ ์๊ฐ ๊ธฐ๋ค๋ฆฌ๋ฉด ๋ฆฌํ์คํธ๋ฅผ ๊ฐ์ํ๋ ์ง์ฐ์คํ Task๋ฅผ ์ ์ํ์ฌ, ์ ๋ ฅ์ด ์์ ๋๋ง๋ค ๊ทธ๊ฒ์ ๊ธฐ๋ํ๊ฒ ๋ฉ๋๋ค. ๋จ, ์ ๋ ฅ์ด ์์์๋์ ์ด๋ฏธ ์ง์ฐ Task๊ฐ ๊ธฐ๋๋์ด ์์ ๋๋, ๋จผ์ ๊ทธ๊ฒ์ ์ทจ์ํ๊ณ ๋์ ์๋ก์ด Task๋ฅผ ๊ธฐ๋์ํฌ ํ์๊ฐ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ง์ฐ์คํ Task๋ ์๋ฌด๋ฆฌ ๋ง์๋ 1๊ฐ๋ง ์คํ๋ฉ๋๋ค. ๊ทธ๋ผ ์ฝ๋๋ฅผ ๋ด ์๋ค.
sagas.js
import { delay } from 'redux-saga';
import { call, put, fork, take } from 'redux-saga/effects';
function* runRequestSuggest(text) {
const { data, error } = yield call(API.suggest, text);
if (data && !error) {
yield put(successSuggest({ data }));
} else {
yield put(failureSuggest({ error }));
}
}
function forkLater(task, ...args) {
return fork(function* () {
yield call(delay, 1000);
yield fork(task, ...args);
});
}
function* handleRequestSuggest() {
let task;
while (true) {
const { payload } = yield take(REQUEST_SUGGEST);
if (task && task.isRunning()) {
task.cancel();
}
task = yield forkLater(runRequestSuggest, payload);
}
}
export default function* rootSaga() {
yield fork(handleRequestSuggest);
}
์ฃผ๋ชฉํ ํฌ์ธํธ๋ 2๊ฐ์
๋๋ค. 1๋ฒ์งธ ํฌ์ธํธ๋ ๋๊ฒจ์ง Task๋ฅผ ์ง์ฐ์ฒ๋ฆฌํ๋ forkLater
ํจ์๋ fork
Effect๋ฅผ ๋๋ ค์ฃผ๋ ํจ์์
๋๋ค. call
Effect๋ก delay
ํจ์๋ฅผ ๋ถ๋ฌ์ ์ผ์ ์๊ฐ์ ๊ธฐ๋ค๋ฆฌ๊ณ , delay
ํจ์๊ฐ ๋๋ ค์ฃผ๋ Promise๊ฐ resolve๋๋ฉด ์ ์ด๊ฐ ๋์์์ Task๋ฅผ fork
ํฉ๋๋ค. ์ฐธ๊ณ ๋ก delay
ํจ์๋ redux-saga
๋ชจ๋๋ก๋ถํฐ ์ฝ์ด๋ค์
๋๋ค. 2๋ฒ์งธ ํฌ์ธํธ๋ handleRequestSuggest
Task์ ์คํ ์ ์ง์ฐ์คํ Task๊ฐ ์๋ ๊ฒฝ์ฐ, ๊ทธ๊ฒ์ ์ทจ์ํ๊ณ ๋์ ๊ธฐ๋์ํค๋ ๋ถ๋ถ์
๋๋ค. fork
Effect๋ฅผ yield
ํ์ ๋ ๋ฆฌํด ๊ฐ์ Task ์ธํฐํ์ด์ค๋ฅผ ๊ฐ์ง๋ ์ค๋ธ์ ํธ๋ก ๊ธฐ๋๋ Task์ ์ํ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ์ทจ์ํ๋ ๋ฑ, ์ด๊ฒ์ ๊ฒ ํ ์ ์์ต๋๋ค.
์ด๋ฌํ ๋ฐฉ๋ฒ์ผ๋ก ์ํ๋ ๋์์ ๋ง๋ค์ด ์ก์ง๋ง, handleRequestSuggest
Task์ "Action์ ๊ธฐ๋ค๋ฆฌ๋ ๋ฆฌํ์คํธ๋ฅผ ์์ํ๋ค"๋ผ๋ ์ญํ ์ด ์์๋ณด๊ธฐ ์ด๋ ค์์ก์ต๋๋ค. ๊ฐ๋ฅํ ์๋์ Task์ฒ๋ผ, ํ๊ณ ์ถ์ ์๋๋ฅผ ์ ํ๋ ์ฝ๋๋ผ๋ฉด ์ข์๊ฒ ์ง์.
before.js
function* handleRequestSuggest() {
while (true) {
const { payload } = yield take(REQUEST_SUGGEST);
yield fork(runRequestSuggest, payload);
}
}
์๋ ์์ฑ์ ๊ธฐ๋ฅ๋ง ์๊ฐํ๋ฉด ๋ฉ์ง๊ฒ ๋์์ผ๋, ์ด์ ์ฝ๋๋ ๋ฉ์ง๊ฒ ๋ง๋ค์ด ๋ด ์๋ค.
์ด๋ป๊ฒ ํ๋๋ฉด, handleRequestSuggest
Task์ ํฉ์ด์ง ์ทจ์๋ฅผ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ ๋ถ๋ฆฌ์ํต๋๋ค. ์ด๋ 1๊ฐ์ Task๋ก ์ฒ๋ฆฌํ๋ ์ผ์ ์ค์ฌ ์ญํ ์ ๋ช
ํํ๊ฒ ํ๊ธฐ ์ํด ์ ๊ทน์ ์ผ๋ก ํด๋ณด๊ณ ์ถ์๋ ๊ฐ์ ์ฌํญ์
๋๋ค.
sagas.js
function* runRequestSuggest(text) {
const { data, error } = yield call(API.suggest, text);
if (data && !error) {
yield put(successSuggest({ data }));
} else {
yield put(failureSuggest({ error }));
}
}
function createLazily(msec = 1000) {
let ongoing;
return function* (task, ...args) {
if (ongoing && ongoing.isRunning()) {
ongoing.cancel();
}
ongoing = yield fork(function* () {
yield call(delay, msec);
yield fork(task, ...args);
});
}
}
function* handleRequestSuggest() {
const lazily = createLazily();
while (true) {
const { payload } = yield take(REQUEST_SUGGEST);
yield fork(lazily, runRequestSuggest, payload);
}
}
export default function* rootSaga() {
yield fork(handleRequestSuggest);
}
handleRequestSuggest
Task๊ฐ ๋งค์ฐ ๊น๋ํด์ก์ต๋๋ค. fork(runRequestSuggest, payload)
์๋ ๋ถ๋ถ์ด fork(lazily, runRequestSuggest, payload)
๋ก ๋ฐ๋์์ ๋ฟ์ด๊ธฐ์ ๋ณํ๋ ๋ง์ง ์์ต๋๋ค. ํ์ง๋ง ์ ์ด๋ ์์ด์ฒ๋ผ "fork lazily"๋ก ์ฝ์ด์ง๊ธฐ์ ์๋๋ฅผ ์ ๋ฌํ๊ธฐ๊ฐ ์ฌ์์ก์ ๊ฒ๋๋ค.
๋ง๋ฒ์ฒ๋ผ ์ง์ฐ ์คํํด์ฃผ๋ lazily
Task์ด์ง๋ง ์ด๊ฒ์ createLazily
ํจ์๋ก ์์ฑํ๊ณ ์์ต๋๋ค. ์คํ์ค์ Task๋ฅผ ์กด์์ํค๊ธฐ ์ํด ํด๋ก์ ธ๋ก ๋ง๋ค ํ์๊ฐ ์์์ต๋๋ค. ํ๋ ์ผ์ ์ด์ ์ ๊ตฌํ๊ณผ ๋์ผํฉ๋๋ค.
์ด๊ฑธ๋ก ๊ธฐ๋ฅ๋ ๊ตฌํ๋ ๋ฉ์ง๊ฒ ๋์์ต๋๋ค.
- ์ง์ฐ์คํ์ด ์์๋๊ธฐ๊น์ง ์๋ฌด๊ฒ๋ ํ์ํ์ง ์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
takeLatest
ํฌํผํจ์๋ฅผ ์จ์ ๋ค์ ์ด๋ค.
๋ฐ๋ชจ: Throttle ์์ : kuy/redux-saga-examples > throttle
">https://github.com/kuy/redux-saga-examples/tree/master/throttle)-->ํฌ์คํ ๋ฆฌ์คํธ์ ๊ฐ์ด ๋ง์ ์ปจํ ์ธ ๋ฅผ ํ๋ฒ์ ์ฝ์ด์์, ๋์ฑ์ด ๊ฐ๊ฐ์ ์ปจํ ์ธ ๋ง๋ค ๋ฆฌํ์คํธ๋ฅผ ์์ฒญํ๋ฉด, ์ปจํ ์ธ ์ ์๋งํผ ๋ฆฌํ์คํธ๋ฅผ ๋์์ ๋ณด๋ด๊ฒ๋๋ ์ฌ๊ฐํ ์ผ์ด ์ผ์ด๋๊ฒ ์ฃ . ์๋ฒ๋ถํ์ ๊ฐ์ ๋ฌธ์ ๊ฐ ์๋ค๊ณ ํด๋, Dos๊ณต๊ฒฉ์ผ๋ก ํ๋จ๋์ด ๋ฆฌํ์คํธ๊ฐ ์ฐจ๋จ๋๋ ์ผ๋ ์์ ๊ฒ๋๋ค. ๋ ํต์ ์ฒ๋ฆฌ์ ๊ฒฝ์ฐ, ๋๋๋ฐ์ํ๋ Action์ ๋ฐ๋ฅธ Task์ ๋์ ์คํ ๊ฐ๋ฅํ ์ ์ด์์ ์์ํ์ง ์๊ณ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋ง๋ค์ด์, ์์ Task๊ฐ ์๋ฃ๋๋ฉด ์์๋๋ก ๋ค์ Task๋ฅผ ์คํ์ํค๋ ํ(Queue)๊ฐ ํ์ํ ๋๊ฐ ์์ต๋๋ค. ์ด๋ฒ ์์ ๋ ๊ธฐ๋์ค์ธ Task์ ์๋ฅผ ์กฐ์ ํ๋ ์ค๋กํ๋ง์ redux-saga๋ก ๊ตฌํํฉ๋๋ค.
sagas.js
const newId = (() => {
let n = 0;
return () => n++;
})();
function something() {
return new Promise(resolve => {
const duration = 1000 + Math.floor(Math.random() * 1500);
setTimeout(() => {
resolve({ data: duration });
}, duration);
});
}
function* runSomething(text) {
const { data, error } = yield call(something);
if (data && !error) {
yield put(successSomething({ data }));
} else {
yield put(failureSomething({ error }));
}
}
function* withThrottle(job, ...args) {
const id = newId();
yield put(newJob({ id, status: 'pending', job, args }));
}
function* handleThrottle() {
while (true) {
yield take([NEW_JOB, RUN_JOB, SUCCESS_JOB, FAILURE_JOB, INCREMENT_LIMIT]);
while (true) {
const jobs = yield select(throttleSelector.pending);
if (jobs.length === 0) {
break; // No pending jobs
}
const limit = yield select(throttleSelector.limit);
const num = yield select(throttleSelector.numOfRunning);
if (limit <= num) {
break; // No rooms to run job
}
const job = jobs[0];
const task = yield fork(function* () {
yield call(job.job, ...job.args);
yield put(successJob({ id: job.id }));
});
yield put(runJob({ id: job.id, task }));
}
}
}
function* handleRequestSomething() {
while (true) {
yield take(REQUEST_SOMETHING);
yield fork(withThrottle, runSomething);
}
}
export default function* rootSaga() {
yield fork(handleRequestSomething);
yield fork(handleThrottle);
}
์๋์์ฑ ์์ ๋ 1๊ฐ์ Task๋ง ๋์์ ์คํ์ํค๊ณ , ์๋ก์ด Task๊ฐ ์ค๋ฉด ์ฒ๋ฆฌ์ค์ธ Task๋ฅผ ์ทจ์์ํค๊ณ ๋์ ๊ธฐ๋์ ํ์์ต๋๋ค. ์ด๋ฏธ Task๊ฐ ๊ธฐ๋์ค์ธ์ง ์๋์ง๋ฅผ ํ์ ํ๊ธฐ ์ํด ์ํ๋ฅผ ๊ฐ์ง ํ์๊ฐ ์์๊ณ , ๊ทธ๊ฒ์ ํด๋ก์ ธ ๋ด์์ ๋ค๋ฃจ๊ฒ ํ๋ ์ดํ๋ก์น์์ต๋๋ค. ์ค๋กํ๋ง์ผ๋ก๋ ์คํ์ค์ Task๋ฅผ ํ์ ํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ฌด์ธ๊ฐ์ ์ํ๋ฅผ ๊ธฐ๋ค๋ฆด ํ์๊ฐ ์๋ ์ ์ด ๊ณตํต๋ฉ๋๋ค. ํ์ง๋ง ์ด๋ฒ์ ๋ค๋ฅธ ์ดํ๋ก์น๋ก, ์ํ๋ฅผ Task ๋ด๋ถ์์ ๊ฐ์ง์ง ์๊ณ , ๋์ Store์๋ค ๋ฃ์ด๋ก๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์คํ ์ํ๊ฐ ๋ทฐ์์ ์ค์๊ฐ ํ์๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
์๋ sagas.js
์ ์ฝ๋๋ง ๋ณด์ฌ์ฃผ์์ง๋ง, ์ด๋ฒ์ ์ํ๋ฅผ Store๊ฐ ๊ฐ์ง๊ฒ ํ๋ฏ๋ก ์ ์ฒด๋ฅผ ์ดํดํ๊ธฐ ์ํด reducers.js
๋ ์ค์ํ๋ ํ๋ฒ ํ์ด๋ด์ฃผ์ธ์.
๊ตฌํ์ ํฌ๊ฒ ๋๋๋ฉด 2๊ฐ์ Task, handleRequestSomething
์ handleThrottle
๋ก ๋๋ฉ๋๋ค. ์ ์๋ REQUEST_SOMETHING
Action์ dispatch๋ฅผ ๊ฐ์ํ์ฌ ์คํํด์ผํ Task๋ง์ ๋ณด๋ด์ค๋๋ค. ํ์๋ ์กฐ๊ธ ๋ณต์กํฉ๋๋ค. handleRequestSomething
Task๋ก ๋ถํฐ ์คํ ์์ฒญ๋ Task๋ฅผ ์ผ๋จ ํ์ ๋ฃ์ด๋๊ณ , ๋์ ์คํ ์๋ฅผ ์กฐ์ ํ๋ฉด์ ์ฒ๋ฆฌํด๊ฐ๋๋ค. ์ค๋กํ๋ง์ด ์๋ ์คํ fork(runSomething)
๊ณผ ์ค๋กํ๋ง์ด ์๋ ์คํ fork(withThrottle, runSomething)
์์ ์ฝ๋์ ์ฐจ์ด๊ฐ ์กฐ๊ธ๋ง ์๋๋ก ๋ง๋ค์์ต๋๋ค.
handleThrottle
Task๋ฅผ ๋ณด๋ฉด ์กฐ๊ธ ๋ฏ์ค์ 2์ค์ while ๋ฃจํ๊ฐ ์์ต๋๋ค. ์ฒซ๋ฒ์งธ ๋ฃจํ๋ ์ต์ํ ํจํด์ด๋ฏ๋ก ๊ด์ฐฎ์ ๊ฒ๋๋ค. 2๋ฒ์งธ ๋ฃจํ๋ ์คํ๊ฐ๋ฅํ job์ ์์ ์ฌ์ ๊ฐ ์๋ํ job์ ์คํ์ ์์ ์ํ ๊ฒ์
๋๋ค. ์ฝ๋์ ๊ฐ๋
์ฑ์ ์ฐ์ ํด์ while ๋ฃจํ๋ก ๋ง๋ค์ด์ ธ ์์ง๋ง, ์คํ ๊ฐ๋ฅํ job์ ์์ ๋๊ธฐ ์ํ์ job์ ๋ง๋ค์ด ํ๋ฒ์ ์คํ์์ผ๋ ๊ด์ฐฎ์ต๋๋ค.
- ๋ค์ค ์คํ ํ
- ์ฐ์ ์์
redux-saga๋ก ์ธ์ฆ์ฒ๋ฆฌ๋ฅผ ์ด๋ป๊ฒ ๋ค๋ฃจ๋์ง๋ฅผ ์๊ฐํด๋ด ์๋ค.
๋ง๋ค๊ณ ์ถ์๊ฑด, ์ ์ ๊ฐ ๋ก๊ทธ์ธํ์ฌ, ์ธ์ฆ๋ฐ๊ณ , ์ฑ๊ณตํ๋ฉด ํ๋ฉด์ ์ด๋ฅผ ํ๊ฑฐ๋, ์คํจํ๋ฉด ๊ทธ ์ด์ ๋ฅผ ํ์ํ๊ณ , ๋ก๊ทธ์์ํ๋ฉด ๋ค์ ๋๊ธฐ์ํ๋ก ๋์๊ฐ๋, ์ธ์ฆ ๋ผ์ดํ์ฌ์ดํด์ ์ ์ฒด์ ๋๋ค. ์ด๋ฌํ ์ฒ๋ฆฌ๋ฅผ ์๋ฒ์ฌ์ด๋์ ๊ตฌํํ๋ฉด, Cookie์ ๊ฐ์ ํ ํฐ์ ๊ฐ์ง๊ณ ๋ ์์จ ๋ฆฌํ์คํธ๊ฐ ์ด๋ค ์ ์ ๋ก๋ถํฐ ์จ ๊ฑด์ง๋ฅผ ์๋ณํ ํ์๊ฐ ์์ต๋๋ค. ์ฆ ์ฒ๋ฆฌ ์์ฒด๋ ๋ฆฌํ์คํธ ๋จ์๋ก ๋์ด ํ ๋ง๋ ์ํ์ ๋๋ค. ๊ทธ๊ฒ์ redux-saga์(๋ญ ์์ ๋ ํผ์์ ํ๋ฏ๋ก ์๋ณํ๋๊ฒ ๊ทธ๋ค์ง ์๋ฏธ์์ง๋ง) Task๊ฐ ์ผ์ ์ ์ง์ํค๋๊ฒ ๊ฐ๋ฅํ๋ค๋ ํน์ง์ ์ด๋ ค์, ์ธ์ฆ ๋ผ์ดํ์ฌ์ดํด ์ ์ฒด๋ฅผ 1๊ฐ์ Task๊ฐ ๊ด๋ฆฌํ๋๋ก ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ํ๋ง๋๋ก ์ธ์ ์ ์ง์ Task๋ฅผ ์ฐ๋ ํ์์ ๋๋ค.
์์ ๋ ์ด๋ป๊ฒ ๋์๊ฐ๋์ง ๋ถ์๊ธฐ ํ์
์ ์ํ ์ฝ๋์
๋๋ค. ์๋ Gist์๋ค ์ด ๊ฒ์
๋๋ค. ๋ก๊ทธ์ธ์ด ์ฑ๊ณตํ๋ฉด react-router-redux
๋ฅผ ์ฌ์ฉํ์ฌ ๋์๋ณด๋ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
sagas.js
import { push } from 'react-router-redux';
function* authSaga() {
while (true) {
// ๋ก๊ทธ์ธ ํ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
const { user, pass } = yield take(REQUEST_LOGIN);
// ์ธ์ฆ์ฒ๋ฆฌ ์์ฒญ (์ฌ๊ธฐ์ try-catch๋ฅผ ์ฐ์ง์๊ณ , ๋ฆฌํด ๊ฐ์ ์๋ฌ์ ๋ณด๊ฐ ํฌํจ๋๋ ์คํ์ผ๏ผ
const { token, error } = yield call(authorize, user, pass);
if (!token && error) {
yield put({ type: FAILURE_LOGIN, payload: error });
continue; // ์ธ์ฆ์ ์คํจํ๋ฉด ์ฌ์๋์ ํจ๊ป ์ฒ์์ผ๋ก ๋์๊ฐ๋๋ค.
}
// ๋ก๊ทธ์ธ ์ฑ๊ณต์ ์ฒ๋ฆฌ (ํ ํฐ ์ ์ง ๋ฑ)
yield put({ type: SUCCESS_LOGIN, payload: token });
// ๋ก๊ทธ์์ ํ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
yield take(REQUEST_LOGOUT);
// ๋ก๊ทธ์์ ์ฒ๋ฆฌ (ํ ํฐ ์ ๋ฆฌ๋ฑ)
yield call(SUCCESS_LOGOUT);
}
}
function* pageSaga() {
while (true) {
// ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
yield take(SUCCESS_LOGIN);
// ๋์๋ณด๋๋ก ์ด๋ํ๋ค.
yield put(push('/dashboard'));
}
}
ํด์ผ ํ ์ผ์ ์ธ์ฆ์ฒ๋ฆฌ์ ๋ผ์ดํ์ฌ์ดํด์ ๊ด๋ฆฌํ๋ ๊ฒ๊ณผ, ๋ก๊ทธ์ธ ์ฑ๊ณต์์ ํ์ด์ง ์ ์ด๋ฅผ ํ๋ 2๊ฐ์
๋๋ค. ์ด๊ฒ๋ค์ ๋ฌผ๋ก 1๊ฐ์ Task๋ก ๊ตฌํ ๊ฐ๋ฅํ์ง๋ง, redux-saga's way(๋ผ๋๊ฒ ์์์ง๋ ๋ชจ๋ฅด์ง๋ง)์ ๋ฐ๋ผ ์ ๋๋ก ์ญํ ๋ณ๋ก Task๋ฅผ ๋๋์ด, ๊ฐ๊ฐ authSaga
์ pageSaga
๋ก์จ ์ ์ํฉ๋๋ค.
์ฌ๊ธฐ๊น์ง์ 2๊ฐ์ ์์ ์์ ํ์์ ๋ฐ๋ผ Task ๋ด๋ถ์ ์ธ๋ถ์ ์ํ๋ฅผ ๊ฐ์ง๊ณ ์์์ต๋๋ค. ์ด๋ฒ ์์ ๋ ์ธ์ฆ์ฒ๋ฆฌ์ ๋ผ์ดํ์ฌ์ดํด์ด ์ด๋๊น์ง ๋์ด์๋๋ฅผ ์ํ๋ก ๊ฐ์ง๋ ๊ฒ์ ์ ๊ทน์ ์ผ๋ก ํ์ฉํ๋ ์์ ๋๋ค. 1๊ฐ์ ์ฒ๋ฆฌ๋ 1๊ฐ์ ํ์คํฌ๊ฐ ํญ์ ๋ถ์ด์๊ฒ ๋๋๋ฐ redux-saga๊ฐ ์ ๊ณตํ๋ ํ์คํฌ ์คํ ํ๊ฒฝ ๋๋ถ์ ๋๋ค. ๋๋ถ์ ์ฝ๋๊ฐ ๋งค์ฐ ์ง๊ด์ ์ผ๋ก ๋ฉ๋๋ค.
- ๋ณต์ ์ธ์ ์ ์ ์ง
์ฌ๊ธฐ์๋ถํฐ ์กฐ๊ธ ๋ค๋ฅธ ์ข ๋ฅ์ ์๋ฅผ ์๊ฐํ๋ ค ํฉ๋๋ค. ๋จผ์ Socket.IO์์ ์ฐ๊ณ์ ๋๋ค.
์์ ์ฝ๋: kuy/redux-saga-chat-examples
๋ฐ์ ์์ ์์ ๊ฐ์ ธ์จ ๋ด์ฉ์ ๋๋ค.
sagas.js
function subscribe(socket) {
return eventChannel(emit => {
socket.on('users.login', ({ username }) => {
emit(addUser({ username }));
});
socket.on('users.logout', ({ username }) => {
emit(removeUser({ username }));
});
socket.on('messages.new', ({ message }) => {
emit(newMessage({ message }));
});
socket.on('disconnect', e => {
// TODO: handle
});
return () => {};
});
}
function* read(socket) {
const channel = yield call(subscribe, socket);
while (true) {
const action = yield take(channel);
yield put(action);
}
}
function* write(socket) {
while (true) {
const { payload } = yield take(`${sendMessage}`);
socket.emit('message', payload);
}
}
function* handleIO(socket) {
yield fork(read, socket);
yield fork(write, socket);
}
function* flow() {
while (true) {
let { payload } = yield take(`${login}`);
const socket = yield call(connect);
socket.emit('login', { username: payload.username });
const task = yield fork(handleIO, socket);
let action = yield take(`${logout}`);
yield cancel(task);
socket.emit('logout');
}
}
export default function* rootSaga() {
yield fork(flow);
}
Socket.IO์ผ๋ก๋ถํฐ ๋ฉ์ธ์ง์ ์์ ์ eventChannel์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๋ฐ์ Socket.IO์ ์ด๋ฒคํธ๋ง๋ค Redux์ Action์ผ๋ก ๋งตํํด์ฃผ์ด put
์ผ๋ก dispatchํ๊ณ ์์ต๋๋ค. ๊ฑฐ๊ธฐ์ Task์ ์ฐ์์ ์ธ ์ทจ์ ์ญ์ ์ฐ์ฌ์ง๊ณ ์์ต๋๋ค.
์ด ์์ ๋ ๋์ถฉ ์๋ํ๋์ง๋ง ๋๋ ค๋ณธ ์ํ์ ๋๋ค. Read/Write์ ๋ถ๋ถ์ด๋ ๋ณต์ ์ฑ๋ ๋์, ํ๋ํ๋ ๋งตํ์ด ๊ท์ฐฎ์์ ์ด๋ฒคํธ์ด๋ฆ์ ๊ทธ๋๋ก Action Types์ผ๋ก ์ฐ๊ฑฐ๋, Socket.IO์ ํต์ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ฑฐ๋, ์ด๋ป๊ฒ ๋ค๋ฃฐ๊ฒ์ธ๊ฐ์ ์์ง ๊ณ ๋ฏผ์ค์ ๋๋ค. ์ธ์ ๊ฐ ๊ทธ๊ฒ๋ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ง๋ค์ด๋์ผ๋ฉด ์ข๊ฒ ๋ค๋ผ๊ณ ์๊ฐํ๊ณ ์์ต๋๋ค.
๋ฐ๋ชจ: Miniblog ์์ ์ฝ๋ kuy/redux-saga-examples > microblog
">https://github.com/kuy/redux-saga-examples/tree/master/microblog)-->์ต์ ๋ํญ ์ ๋ฐ์ดํธ๊ฐ ์์๋ Firebase๋ฅผ ์จ์ ์ํ์ผ์ redux-saga์ ์ฐ๊ณ์ํจ ์์ ์ ๋๋ค. ํ ๋ง๋ ํธ์ํฐ์ ๊ฐ์ ๋ฏธ๋ ๋ธ๋ก๊ทธ ์๋น์ค์ด์ง๋ง, ๊ตฌํ์ด ๋๋์ ์ฑํ ์ฑ์ฒ๋ผ ๋์ด์์ต๋๋ค... ๋ธ๋ผ์ฐ์ ์ ์ํฌ๋ฆฟ๋ชจ๋์ ์ฌ๋ฌ๊ฐ๋ฅผ ์ด์ด ๊ฐ๊ฐ์ ์ด๋ฆ์ผ๋ก ํฌ๊ณ ํ๋ฉด ์ค์๊ฐ์ผ๋ก ๊ฐฑ์ ๋ฉ๋๋ค.
์ด๋ฌํ ์์ ๋ ์์ ์ด์์ ์๋ฏธ๋ ์์ต๋๋ค. ์ด๋ป๊ฒ redux-saga๋ก Firebase๋ Socket.IO๋ฅผ ์ธ ๊ฒ์ธ๊ฐ์ ๋๋ฌด ์ง์ฐฉํ์ง๋ ๋ง์์ฃผ์ธ์. ์๋๋ฉด redux-saga๋ ๊ธฐ๋ฅ์ ์ผ๋ก Middleware์ ์๋ธ์ ์ด๋ฏ๋ก, redux-saga๋ก ํ ์ ์๋๊ฑด Middleware๋ก๋ ๊ฐ๋ฅํฉ๋๋ค. ๊ฒ๋ค๊ฐ redux-saga๋ก ๋ง๋ค๋ฉด, ํ๋ก์ ํธ์ ๋์ ํ ๋ redux-saga๊ฐ ํ์๊ฐ ๋ฉ๋๋ค. Middleware๋ก ๊ฐ๋ฅํ ๊ฒ์ redux-saga๋ก ๋ง๋ค์ด์, ๋์ ์ฅ๋ฒฝ์ ๋์ด๊ฒ ๋๋ฉด ์๋ฏธ๊ฐ ์์ต๋๋ค. ์ด๋ฌํ ๊ธฐ๋ฅ์ ์์ํ๊ฒ Middleware๋ Store Enhancer ์์ค์ผ๋ก ๊ตฌํํ๋๊ฒ์ด ์ข์ง ์์๊น ์ถ์ต๋๋ค.
redux-saga-chat-example์ด๋ผ๋ redux-saga์ Socket.IO๋ฅผ ํฉ์น ์ฑํ ์ฑ์ ๋ง๋๋, ์์ธ์ง PubNub์ ํจ๊ป ์ธ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ์ง?๋ผ๋ ์ง๋ฌธ ์์ด์ ์์ ๋ฅผ ์จ๋ดค์ต๋๋ค.
redux-saga์ ์ฌ์ฉ๋ฒ์ ๋ค์ํ ๊ฐ๋์์ ๋ดค์ต๋๋ค. ๋ญ๋ ํ ์ ์์ ๊ฒ ๊ฐ์ง๋ง, redux-saga์๋ ์ ์ฝ์ด ์์ต๋๋ค. ๋ชจ๋ Middleware์ ์ฒ๋ฆฌ๋ฅผ ์ด์ํ๋๊ฑด ๋ถ๊ฐ๋ฅ ํฉ๋๋ค. ์๋ฅผ๋ค์ด Middleware์ฒ๋ผ Action์ ์์ ๋ด๋ ๊ฑด ๋ชปํฉ๋๋ค. ๊ทธ๋์ ์ด๋ฒ ์ํ์ Redux์ middleware๋ฅผ ์ ๊ทน์ ์ผ๋ก ์จ๋ณด์์์ ์๊ฐํ Action์ Dispatchํ๊ธฐ ์ ์ ๋ธ๋ผ์ฐ์ ํ์ธ ๋ค์ด์ผ๋ก๊ทธ๋ฅผ ํ์ํ์๋ฅผ ๊ทธ๋๋ก ์ด์ํ๋๊ฑด ๋ถ๊ฐ๋ฅํ์ต๋๋ค. ๊ฐ์ ์ผ์ ํ๊ธฐ์ ์ฐ์ ๋ค๋ฅธ Action์ dispatch์์ผ, ํ์ธ ๋ค์ด๋ด๋ก๊ทธ์ Yes๊ฐ ๋์ค๋ฉด ์๋์ Action์ dispatchํ๋ ์์ ๋ณ๊ฒฝ์ด ํ์ํ์ต๋๋ค. ์ด๋ฌ๋ฉด ๋ณธ๋ง์ ๋๊ฐ ๋๋ฏ๋ก ๊ทธ๋ฅ Middleware๋ฅผ ์ฐ๋ ๊ฒ ์ข์ ๊ฒฝ์ฐ์ ๋๋ค. ๋ง๋ถ์ฌ ์ด๋ฌํ ์ ์ฝ์ Redux Middleware in Depth๋ผ๋ ํฌ์คํ ์๋ ํด์คํ Middleware๋ฅผ ์คํํ๋ ํ์ด๋ฐ ๋๋ฌธ์ ์ผ์ด๋๋ ๊ฒ์ ๋๋ค. redux-saga์ ๊ฒฝ์ฐ, ํญ์ Reducer์ ์ฒ๋ฆฌ๊ฐ ๋๋ ๋ค์์ Saga๊ฐ ์คํ๋๋ฏ๋ก, ์ง๊ธ ์ํ๋ก๋ ์ด๋ป๊ฒ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์์๊ฐ ์์์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง redux-saga์ issue๋ฅผ ์ธ์๋ณผ๊น ์๊ฐํ๊ณ ์์ต๋๋ค.
redux-saga๋ฅผ ์ฐ๋ ๊ฒ์ผ๋ก redux-thunk๋ Middleware๋ณด๋ค๋ ๊ตฌ์กฐํ๋ ์ฝ๋๋ก ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ Task๋ผ๋ ์คํ๋จ์๋ก ๊ธฐ์ ํ๋ ๊ฒ์ด ๊ฐ๋ฅํด์ง๋๋ค. ๊ฑฐ๊ธฐ์ Mock์ ์จ์ผํ๋ ํ ์คํธ๋ฅผ ์ค์ด๊ณ , ํ ์คํธํ๊ณ ์ถ์ ๋ก์ง์ ์ง์ค ํ ์ ์์ต๋๋ค. ๋ํ, ์ฌ์ด์ฉ๊ฐ๋ฅํ ์ปดํฌ๋ํธ์ ๊ฐ๋ฐ์์๋ ํ์ํ ๋น๋๊ธฐ์ฒ๋ฆฌ๋ฅผ redux-saga์ Task๋ก์ ์ ๊ณตํ๋ฉด, Middleware๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ ์ผ์ด๋๋ ์คํ์์์ ๋ฌธ์ ๋ฅผ ํผํ ์ ์์ด ์์ ํฉ๋๋ค. ํ์ง๋ง ๋ชจ๋ Middleware๋ฅผ redux-saga๋ก ๋ฐ๊พธ๋๊ฑด ๋ถ๊ฐ๋ฅํ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํฉ๋๋ค.