redux-saga / redux-saga์ redux ํ์ด์์ ์ต์ ์์ด์ ๋ํด ๋ง์ ์ด์ผ๊ธฐ๊ฐ ์์ต๋๋ค. ์์ ๋ฃ๊ธฐ / ๋ฐฐํฌ๋ฅผ ์ํด ์์ฑ๊ธฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํฉ๋๋ค.
๋จธ๋ฆฌ๋ฅผ ๊ฐ์ธ๊ธฐ ์ ์ async / await๊ณผ ํจ๊ป redux-saga์ฌ์ฉ redux-thunkํ๋ ์๋์ ์ ๊ทผ ๋ฐฉ์ ๋์ ์ฌ์ฉ์ ์ฅ๋จ์ ์ ์๊ณ ์ถ์ต๋๋ค .
๊ตฌ์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ์ ์ ์์ผ๋ฉฐ ํ์์ ๊ฐ์ด ์์ ์ ์ ๋ฌํฉ๋๋ค.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
๊ทธ๋ฐ ๋ค์ ๋ด ํ๋์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
๋ต๋ณ
redux-saga์์ ์์ ์์ ๋๋ฑํ ๊ฒ์
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
๊ฐ์ฅ ๋จผ์ ์์์ผ ํ ๊ฒ์ form์ ์ฌ์ฉํ์ฌ api ํจ์๋ฅผ ํธ์ถํ๋ค๋ ๊ฒ yield call(func, ...args)์
๋๋ค. callํจ๊ณผ๋ฅผ ์คํํ์ง ์๊ณ ์ ๊ฐ์ ์ผ๋ฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ญ๋๋ค {type: 'CALL', func, args}. ์คํ์ ๊ธฐ๋ฅ์ ์คํํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ก ์์ฑ๊ธฐ๋ฅผ ๋ค์ ์์ํ๋ redux-saga ๋ฏธ๋ค์จ์ด์ ์์๋ฉ๋๋ค.
์ฃผ์ ์ด์ ์ ๊ฐ๋จํ ๋ฑ์ ๊ฒ์ฌ๋ฅผ ์ฌ์ฉํ์ฌ Redux ์ธ๋ถ์์ ๋ฐ์ ๊ธฐ๋ฅผ ํ ์คํธ ํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
์ฐ๋ฆฌ๋ ๋จ์ํ ๋ชจ์ ๋ฐ์ดํฐ๋ฅผ next๋ฐ๋ณต์ ์ ๋ฉ์๋์ ์ฃผ์
ํ์ฌ API ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ์ํ๊ณ ์์ต๋๋ค . ๋ฐ์ดํฐ ๋ชจ์๋ ๋ชจ์ ํจ์๋ณด๋ค ํจ์ฌ ๊ฐ๋จํฉ๋๋ค.
๋ ๋ฒ์งธ๋ก ์์์ผ ํ ๊ฒ์์ ๋ํ ํธ์ถ yield take(ACTION)์
๋๋ค. ์ฝ ํฌ๋ ๊ฐ๊ฐ์ ์๋ก์ด ์ก์
(์ :)์์ ์ก์
์ ์์๊ฐ ํธ์ถํฉ๋๋ค LOGIN_REQUEST. ์ฆ, ์ก์
์ ์ง์์ ์ผ๋ก ์ฝํฌ์ ํธ์ ๋๋ฉฐ ์ฝ ํฌ๋ ์ด๋ฌํ ์ก์
์ ์ฒ๋ฆฌ๋ฅผ ์ค์ง ํ ์๊ธฐ๋ฅผ ์ ์ด ํ ์ ์์ต๋๋ค.
REDUX โ ์ฌ๊ฐ์์, ๋ฐ์ ๊ธฐ๋ ํ ๋ค์ ์กฐ์น๋ฅผ. ์ฆ, ์ด๋ค ํ๋์๋ค์ ๋์ํ์ง ๋ง์์ผ ํ ์๊ธฐ๋ฅผ ํต์ ํ ์์๋ค. ์์ ์์ ์์ ํ๋ฆ ๋ช
๋ น์ while(true)๋ฃจํ ๋ด๋ถ์ ๋ฐฐ์น ๋๋ฏ๋ก ๊ฐ ๋ค์ด์ค๋ ๋์์ ์์ ํ์ฌ ์ฝํฌ ํธ์ ๋์์ ๋ชจ๋ฐฉํฉ๋๋ค.
ํ ์ ๊ทผ ๋ฐฉ์์ผ๋ก ๋ณต์กํ ์ ์ด ํ๋ฆ์ ๊ตฌํํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์ ์๊ตฌ ์ฌํญ์ ์ถ๊ฐํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
-
LOGOUT ์ฌ์ฉ์ ์กฐ์น ์ฒ๋ฆฌ
-
์ฒซ ๋ฒ์งธ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด ์๋ฒ๋
expires_inํ๋์ ์ ์ฅ๋ ์ง์ฐ ์๊ฐ์ด ์ง๋ ํ ํฐ์ ๋ฐํ ํฉ๋๋ค.expires_in๋ฐ๋ฆฌ ์ด ๋ง๋ค ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ธ์ฆ์ ์๋ก ๊ณ ์นจํด์ผํฉ๋๋ค. -
API ํธ์ถ์ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆด ๋ (์ด๊ธฐ ๋ก๊ทธ์ธ ๋๋ ์๋ก ๊ณ ์นจ) ์ฌ์ฉ์๊ฐ ์ค๊ฐ์ ๋ก๊ทธ ์์ ํ ์ ์์์ ๊ณ ๋ คํ์ญ์์ค.
์ฝ ํฌ๋ก ์ด๋ป๊ฒ ๊ตฌํํ๊ฒ ์ต๋๊น? ๋ํ ์ ์ฒด ํ๋ฆ์ ๋ํ ์ ์ฒด ํ ์คํธ ๋ฒ์๋ฅผ ์ ๊ณตํฉ๋๊น? Sagas์์ ์ด๋ป๊ฒ ๋ณด์ผ ์ ์์ต๋๊น?
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
์์ ์์์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋์์ฑ ์๊ตฌ ์ฌํญ์ ํํํ๊ณ race์์ต๋๋ค. ๊ฒฝ์ฐ take(LOGOUT)์น๋ฆฌ ๊ฒฝ์ฃผ (์ฆ, ์ฌ์ฉ์๊ฐ ๋ก๊ทธ ์์ ๋ฒํผ ํด๋ฆญ). ๋ ์ด์ค๋ authAndRefreshTokenOnExpiry๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ ์๋์ผ๋ก ์ทจ์ํฉ๋๋ค . ๊ทธ๋ฆฌ๊ณ ํตํ authAndRefreshTokenOnExpiry๋์ค์์ด ์ฐจ๋จ ๋ ๊ฒฝ์ฐ call(authorize, {token})์๋ ์ทจ์๋ฉ๋๋ค. ์ทจ์๋ ์๋์ผ๋ก ์๋์ชฝ์ผ๋ก ์ ํ๋ฉ๋๋ค.
์์ ํ๋ฆ์ ๋ํ ์คํ ๊ฐ๋ฅํ ๋ฐ๋ชจ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค
๋ต๋ณ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฑ์์ ์ฒ ์ ํ ๋ต๋ณ ์ธ์๋ ํ๋ก๋์ ์์คํ ์์ saga๋ฅผ ์ฌ์ฉํ ๊ฒฝํ์ ์ถ๊ฐ ํ ๊ฒ์ ๋๋ค.
ํ๋ก (์ฌ๊ฐ ์ฌ์ฉ) :
-
ํ ์คํธ ๊ฐ๋ฅ์ฑ. call ()์ด ์์ํ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ sagas๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ๋งค์ฐ ์ฝ์ต๋๋ค. ์ฝํฌ๋ฅผ ํ ์คํธํ๋ ค๋ฉด ์ผ๋ฐ์ ์ผ๋ก ํ ์คํธ ๋ด์ mockStore๋ฅผ ํฌํจํด์ผํฉ๋๋ค.
-
redux-saga์๋ ์์ ์ ๋ํ ์ ์ฉํ ๋์ฐ๋ฏธ ๊ธฐ๋ฅ์ด ๋ง์ด ์์ต๋๋ค. saga์ ๊ฐ๋ ์ ์ฑ์ ๋ํ ์ผ์ข ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ปค / ์ค๋ ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
-
Sagas๋ ๋ชจ๋ ๋ถ์์ฉ์ ์ฒ๋ฆฌ ํ ์์๋ ๋ ๋ฆฝ์ ์ธ ์ฅ์๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ด ๊ฒฝํ์์ ์ฝํฌ ์์ ๋ณด๋ค ์์ ํ๊ณ ๊ด๋ฆฌํ๋ ๊ฒ์ด ๋ ์ฝ์ต๋๋ค.
๋ฒ์ฃ์:
-
์์ฑ๊ธฐ ๊ตฌ๋ฌธ.
-
๋ฐฐ์ธ ๊ฐ๋ ์ด ๋ง์ต๋๋ค.
-
API ์์ ์ฑ. redux-saga๋ ์ฌ์ ํ ๊ธฐ๋ฅ (์ : ์ฑ๋?)์ ์ถ๊ฐํ๊ณ ์์ผ๋ฉฐ ์ปค๋ฎค๋ํฐ๋ ๊ทธ๋ฆฌ ํฌ์ง ์์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ธ์ ๊ฐ ์ด์ ๋ฒ์ ๊ณผ ํธํ๋์ง ์๋ ์ ๋ฐ์ดํธ๋ฅผ ๋ง๋๋ ๊ฒฝ์ฐ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
๋ต๋ณ
๋ด ๊ฐ์ธ์ ์ธ ๊ฒฝํ (sagas์ thunk๋ฅผ ๋ชจ๋ ์ฌ์ฉํ์ฌ)์ ๋ํ ์๊ฒฌ์ ์ถ๊ฐํ๊ณ ์ถ์ต๋๋ค.
Sagas๋ ํ ์คํธํ๊ธฐ์ ์ข์ต๋๋ค.
- ํจ๊ณผ๋ก ๊ฐ์ธ ์ง ํจ์๋ฅผ ์กฐ๋กฑ ํ ํ์๊ฐ ์์ต๋๋ค.
- ๋ฐ๋ผ์ ํ ์คํธ๋ ๊นจ๋ํ๊ณ ์ฝ๊ธฐ ์ฝ๊ณ ์์ฑํ๊ธฐ ์ฝ์ต๋๋ค.
- sagas๋ฅผ ์ฌ์ฉํ ๋ ์ก์ ์ ์์๋ ๋๋ถ๋ถ ์ผ๋ฐ ๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ๋ฐํํฉ๋๋ค. ๋ํ ์ฝํฌ์ ์ฝ์๊ณผ ๋ฌ๋ฆฌ ํ ์คํธํ๊ณ ์ฃผ์ฅํ๋ ๊ฒ์ด ๋ ์ฝ์ต๋๋ค.
Sagas๋ ๋ ๊ฐ๋ ฅํฉ๋๋ค. ํ๋์ ์ฝํฌ์ ์ก์ ์ ์์์์ ํ ์์๋ ๋ชจ๋ ๊ฒ ๋ํ ํ๋์ ์ฌ๊ฐ์์ ํ ์ ์์ง๋ง ๊ทธ ๋ฐ๋๋ ์๋๋๋ค (๋๋ ์ ์ด๋ ์ฝ์ง๋ ์์ต๋๋ค). ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ก์
/ ์ก์
์ด ํ๊ฒฌ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค (
take) - ๋ฃจํด์ ๊ธฐ์กด์ ์ทจ์ (
cancel,takeLatest,race) - ์ฌ๋ฌ ๋ฃจํด์ด ๊ฐ์ ํ๋์๋ค์ ์ ์์ต๋๋ค (
take,takeEvery, โฆ)
Sagas๋ ๋ํ ๋ช ๊ฐ์ง ์ผ๋ฐ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ํจํด์ ์ผ๋ฐํํ๋ ๋ค๋ฅธ ์ ์ฉํ ๊ธฐ๋ฅ๋ ์ ๊ณตํฉ๋๋ค.
channels์ธ๋ถ ์ด๋ฒคํธ ์์ค (์ : ์น ์์ผ)๋ฅผ ์ฒญ์ทจ- ํฌํฌ ๋ชจ๋ธ (
fork,spawn) - ์กฐ์ ํ
- โฆ
Sagas๋ ์๋ํ๊ณ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ๊ทธ๋ฌ๋ ๊ถ๋ ฅ์๋ ์ฑ ์์ด ๋ฐ๋ฅธ๋ค. ์์ฉ ํ๋ก๊ทธ๋จ์ด ์ปค์ง๋ฉด ์์ ์ด ์ ๋ฌ๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์ฌ๋ ๋๋ ์์ ์ด ์ ๋ฌ ๋ ๋ ๋ชจ๋ ์ผ์ด ๋ฐ์ํ๋์ง ํ์ ํ์ฌ ์ฝ๊ฒ ์์ค ๋ ์ ์์ต๋๋ค. ๋ฐ๋ฉด์ ์ฝ ํฌ๋ ๋ ๋จ์ํ๊ณ ์ถ๋ก ํ๊ธฐ ์ฝ์ต๋๋ค. ํ๋ ๋๋ ๋ค๋ฅธ ๊ฒ์ ์ ํํ๋ ๊ฒ์ ํ๋ก์ ํธ์ ์ ํ ๋ฐ ํฌ๊ธฐ, ํ๋ก์ ํธ๊ฐ ํ ์ ํธ๋ฅผ ์ฒ๋ฆฌํ๊ฑฐ๋ ๊ฐ๋ฐํด์ผํ๋ ๋ถ์์ฉ์ ์ ํ๊ณผ ๊ฐ์ ๋ง์ ์ธก๋ฉด์ ๋ฌ๋ ค ์์ต๋๋ค. ์ด์จ๋ ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฐ๋จํ๊ณ ์์ธก ๊ฐ๋ฅํ๊ฒ ์ ์งํ์ญ์์ค.
๋ต๋ณ
๊ฐ์ธ์ ์ธ ๊ฒฝํ :
-
์ฝ๋ฉ ์คํ์ผ๊ณผ ๊ฐ๋ ์ฑ์ ์ํด ๊ณผ๊ฑฐ์ redux-saga๋ฅผ ์ฌ์ฉํ๋ ๊ฐ์ฅ ํฐ ์ฅ์ ์ค ํ๋๋ redux-thunk์์ ์ฝ๋ฐฑ ์ง์ฅ์ ํผํ๋ ๊ฒ์ ๋๋ค. ๋ ์ด์ ์ค์ฒฉ์ ์ฌ์ฉํ์ง ์์๋๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด์ async / await ์ฝํฌ๊ฐ ๋์คํ๋๋ฉด์ redux-thunk๋ฅผ ์ฌ์ฉํ ๋ ๋๊ธฐํ ์คํ์ผ๋ก ๋น๋๊ธฐ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ผ๋ฉฐ, ์ด๋ redux-think์ ๊ฐ์ ์ผ๋ก ๊ฐ์ฃผ ๋ ์ ์์ต๋๋ค.
-
redux-saga๋ฅผ ์ฌ์ฉํ ๋, ํนํ Typescript์์ ํจ์ฌ ๋ ๋ง์ ์์ฉ๊ตฌ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํ์น ๋น๋๊ธฐ ํจ์๋ฅผ ๊ตฌํํ๋ ค๋ ๊ฒฝ์ฐ ํ๋์ ๋จ์ผ FETCH ์กฐ์น๋ฅผ ์ฌ์ฉํ์ฌ action.js์์ ํ๋์ ์ฝํฌ ๋จ์๋ก ๋ฐ์ดํฐ ๋ฐ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์ง์ ์ํ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ redux-saga์์๋ FETCH_START, FETCH_SUCCESS ๋ฐ FETCH_FAILURE ์์ ๋ฐ ๋ชจ๋ ๊ด๋ จ ์ ํ ๊ฒ์ฌ๋ฅผ ์ ์ํด์ผ ํ ์ ์์ต๋๋ค. redux-saga์ ๊ธฐ๋ฅ ์ค ํ๋๋ ์ด๋ฌํ ์ข ๋ฅ์ ํ๋ถํ โํ ํฐโ๋ฉ์ปค๋์ฆ์ ์ฌ์ฉํ์ฌ ํจ๊ณผ๋ฅผ ๋ง๋ค๊ณ ์ง์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ฌ์ด ํ ์คํธ๋ฅผ์ํ redux store. ๋ฌผ๋ก ์ด๋ฌํ ํ๋์ ์ฌ์ฉํ์ง ์๊ณ ์ฌ๊ฐ๋ฅผ ์ธ ์๋ ์์ง๋ง ๊ทธ๊ฒ์ ์ฝํฌ์ ๋น์ทํ๊ฒ ๋ง๋ญ๋๋ค.
-
ํ์ผ ๊ตฌ์กฐ์ ๊ด์ ์์, redux-saga๋ ๋ง์ ๊ฒฝ์ฐ ๋ ๋ถ๋ช ํ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ๋ชจ๋ sagas.ts์์ ๋น๋๊ธฐ ๊ด๋ จ ์ฝ๋๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์์ง๋ง redux-thunk์์๋ ๋์์์ ์ฝ๋๋ฅผ ๋ณผ ํ์๊ฐ ์์ต๋๋ค.
-
์ฌ์ด ํ ์คํธ๋ redux-saga์ ๋ ๋ค๋ฅธ ๊ฐ์ค์น ๊ธฐ๋ฅ ์ผ ์ ์์ต๋๋ค. ์ด๊ฒ์ ์ ๋ง ํธ๋ฆฌํฉ๋๋ค. ๊ทธ๋ฌ๋ redux-saga โํธ์ถโํ ์คํธ๋ ํ ์คํธ์์ ์ค์ API ํธ์ถ์ ์ํํ์ง ์์ผ๋ฏ๋ก API ํธ์ถ ํ์ ์ฌ์ฉํ ์์๋ ๋จ๊ณ์ ๋ํ ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ง์ ํด์ผํฉ๋๋ค. ๋ฐ๋ผ์ redux-saga๋ก ์์ฑํ๊ธฐ ์ ์ saga ๋ฐ ํด๋น sagas.spec.ts๋ฅผ ์์ธํ ๊ณํํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
-
Redux-saga๋ ๋ณ๋ ฌ ์์ ์คํ, takeLatest / takeEvery, ํฌํฌ / ์คํฐ ๊ฐ์ ๋์์ฑ ๋์ฐ๋ฏธ์ ๊ฐ์ ๋ง์ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.์ด ๊ธฐ๋ฅ์ ์ฝํฌ๋ณด๋ค ํจ์ฌ ๊ฐ๋ ฅํฉ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ๊ฐ์ธ์ ์ผ๋ก ๋งํ๊ณ ์ถ์ต๋๋ค. ๋ง์ ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ์ ์ค์ํ ์ฑ์์ async / await ์คํ์ผ redux-thunk์ ํจ๊ปํ์ญ์์ค. ๋ง์ ์์ฉ๊ตฌ ์ฝ๋ / ์ก์ / ํ์ ์ ์๋ฅผ ์ ์ฅํ๊ณ ์ฌ๋ฌ ๊ฐ์ง sagas.ts๋ฅผ ์ ํํ๊ณ ํน์ sagas ํธ๋ฆฌ๋ฅผ ์ ์งํ ํ์๊ฐ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋งค์ฐ ๋ณต์กํ ๋น๋๊ธฐ ๋ก์ง๊ณผ ๋์์ฑ / ๋ณ๋ ฌ ํจํด๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ด ํ์ํ ๋ํ ์ฑ์ ๊ฐ๋ฐํ๊ฑฐ๋ ํ ์คํธ ๋ฐ ์ ์ง ๊ด๋ฆฌ (ํนํ ํ ์คํธ ์ค์ฌ ๊ฐ๋ฐ)์ ๋ํ ์์๊ฐ ๋์ ๊ฒฝ์ฐ redux-sagas๋ ์๋ช ์ ๊ตฌํ ์ ์์ต๋๋ค. .
์ด์จ๋ redux-saga๋ redux ์์ฒด๋ณด๋ค ์ด๋ ต๊ณ ๋ณต์กํ์ง ์์ผ๋ฉฐ ํต์ฌ ๊ฐ๋ ๊ณผ API๊ฐ ์ ํ๋์ด ์๊ธฐ ๋๋ฌธ์ ์์ ๊ฐํ๋ฅธ ํ์ต ๊ณก์ ์ด ์์ต๋๋ค. redux-saga๋ฅผ ๋ฐฐ์ฐ๋ ๋ฐ ์ฝ๊ฐ์ ์๊ฐ์ ํฌ์ํ๋ฉด ์์ผ๋ก ์ธ์ ๊ฐ๋ ๋์์ด ๋ ์ ์์ต๋๋ค.
๋ต๋ณ
๋ด ๊ฒฝํ์์ ๋ช ๊ฐ์ง ๋ค๋ฅธ ๋๊ท๋ชจ React / Redux ํ๋ก์ ํธ๋ฅผ ๊ฒํ ํ Sagas๋ ๊ฐ๋ฐ์์๊ฒ ํ ์คํธํ๊ธฐ๊ฐ ๋ ์ฝ๊ณ ์๋ชปํ๊ธฐ ์ด๋ ค์ด ์ฝ๋๋ฅผ ์์ฑํ๋๋ณด๋ค ์ฒด๊ณ์ ์ธ ๋ฐฉ๋ฒ์ ๊ฐ๋ฐ์์๊ฒ ์ ๊ณตํฉ๋๋ค.
๊ทธ๋ ์ต๋๋ค. ์์ํ๊ธฐ์๋ ์กฐ๊ธ ์ด๋ฆฌ์์ง ๋ง ๋๋ถ๋ถ์ ๊ฐ๋ฐ์๋ ํ๋ฃจ ๋ง์ ์ดํดํด์ผํฉ๋๋ค. ๋๋ ํญ์ ์ฌ๋๋ค์๊ฒ ๋ฌด์ yield์ ์์ ํด์ผํ๋์ง ๊ฑฑ์ ํ์ง ๋ง๋ผ๊ณ ๋งํ๊ณ ์ผ๋จ ํ
์คํธ๋ฅผ ๋ ๋ฒํ๋ฉด ๋น์ ์๊ฒ ์ฌ ๊ฒ์
๋๋ค.
๋๋ ์ฝํฌ๊ฐ MVC ํจํผ์ ์ปจํธ๋กค๋ฌ ์ธ ๊ฒ์ฒ๋ผ ์ทจ๊ธ๋๋ ๋ช ๊ฐ์ง ํ๋ก์ ํธ๋ฅผ ๋ณด์์ต๋๋ค. ์ด๊ฒ์ ๋น ๋ฅด๊ฒ ๊ด๋ฆฌ ํ ์์๋ ํผ๋์ด๋ฉ๋๋ค.
๋ด ์กฐ์ธ์ A๊ฐ ๋จ์ผ ์ด๋ฒคํธ์ ๊ด๋ จ๋ B ์ ํ ๋ฌผ๊ฑด์ ํธ๋ฆฌ๊ฑฐ ํด์ผํ๋ ๊ณณ์์ Sagas๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ์ฌ๋ฌ ๊ฐ์ง ์กฐ์น๋ฅผ ์ทจํ ์์๋ ๊ฒ์ ๊ณ ๊ฐ ๋ฏธ๋ค์จ์ด๋ฅผ ์์ฑํ๊ณ FSA ์กฐ์น์ ๋ฉํ ํน์ฑ์ ์ฌ์ฉํ์ฌ ํธ๋ฆฌ๊ฑฐํ๋ ๊ฒ์ด ๋ ์ฝ๋ค๋ ๊ฒ์ ์์์ต๋๋ค.
๋ต๋ณ
์ฝํฌ ๋ ์ฌ ๊ฐ์ค
Redux-Thunk๊ทธ๋ฆฌ๊ณ Redux-Saga๋ช ๊ฐ์ง ์ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ๋ค๋ฅธ ๋ ๋์ ์ค๋ ๋ฏธ๋ค์จ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ (๋์ ์ค๋ ๋ฏธ๋ค์จ์ด ์ฐจ๋จ ์กฐ์น๊ฐ ๋ฐ์ก () ๋ฉ์๋๋ฅผ ํตํด ์ ์ฅ์๋ก ๋ค์ด์ค๋ ๊ฒ์ ์ฝ๋)์
๋๋ค.
์ก์ ์ ๋ง ๊ทธ๋๋ก ์ด๋ค ๊ฒ์ด ๋ ๊ฐ๋ฅํ์ง๋ง ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ ์ก์ ์ ์ ํ ํ๋์ ์ ํ์ ํ์ด๋ก๋, ๋ฉํ ๋ฐ ์ค๋ฅ ํ๋๊ฐ์๋ ์ผ๋ฐ ์๋ฐ ์คํฌ๋ฆฝํธ ๊ฐ์ฒด์ ๋๋ค. ์ :
const loginRequest = {
type: 'LOGIN_REQUEST',
payload: {
name: 'admin',
password: '123',
}, };
Redux-Thunk
Redux-Thunk๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ค ์กฐ์น ๋์คํจ์น ์ธ์๋ ์ด๋ผ๋ ํน์ ํจ์๋ฅผ ๋์คํจ์น ํ ์ ์์ต๋๋ค thunks.
์ฝํฌ (Redux)๋ ์ผ๋ฐ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ต๋๋ค.
export const thunkName =
parameters =>
(dispatch, getState) => {
// Your application logic goes here
};
์ฆ, a thunk๋ ์ ํ์ ์ผ๋ก ์ผ๋ถ ๋งค๊ฐ ๋ณ์๋ฅผ ์ฌ์ฉํ๊ณ ๋ค๋ฅธ ํจ์๋ฅผ ๋ฐํํ๋ ํจ์์
๋๋ค. ๋ด๋ถ ํจ์๋ a dispatch function์ ํจ์๋ฅผ ์ทจํฉ๋๋ค. getState๋ ๋ค Redux-Thunk๋ฏธ๋ค์จ์ด์์ ์ ๊ณตํฉ๋๋ค .
๋ ๋ ์ฌ๊ฐ
Redux-Saga๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์
๋ก์ง์ sagas๋ผ๋ ์์ํ ํจ์๋ก ํํํ ์ ์์ต๋๋ค. ์์ํ ๊ธฐ๋ฅ์ ํ
์คํธ ๊ด์ ์์ ๋ฐ๋์งํ๊ณ ์์ธก ๊ฐ๋ฅํ๊ณ ๋ฐ๋ณต ๊ฐ๋ฅํ๋ฏ๋ก ํ
์คํธํ๊ธฐ๊ฐ ๋น๊ต์ ์ฝ์ต๋๋ค.
Sagas๋ ๋ฐ์ ๊ธฐ ๊ธฐ๋ฅ์ด๋ผ๋ ํน์ ๊ธฐ๋ฅ์ ํตํด ๊ตฌํ๋ฉ๋๋ค. ์ด๊ฒ๋ค์์ ์๋ก์ด ๊ธฐ๋ฅ์
๋๋ค ES6 JavaScript. ๊ธฐ๋ณธ์ ์ผ๋ก yield ๋ฌธ์ ๋ณด๋ ๊ณณ๋ง๋ค ์คํ์ด ์์ฑ๊ธฐ ์ํ์ผ๋ก ์ ํํฉ๋๋ค. yield๋ช
๋ น๋ฌธ์ด ์์ฑ๊ธฐ๋ฅผ ์ผ์ ์ค์งํ๊ณ ์์ฑ ๋ ๊ฐ์ ๋ฆฌํดํ๋ ๊ฒ์ผ๋ก ์๊ฐํ์ญ์์ค . ๋์ค์ ํธ์ถ์๋ ๋ค์์ ๋์ค๋ ๋ฌธ์์ ์์ฑ๊ธฐ๋ฅผ ๋ค์ ์์ํ ์ ์์ต๋๋ค yield.
์์ฑ๊ธฐ ํจ์๋ ์ด์ ๊ฐ์ด ์ ์๋ฉ๋๋ค. function ํค์๋ ๋ค์์ ๋ณํ๊ฐ ํ์๋ฉ๋๋ค.
function* mySaga() {
// ...
}
์ผ๋จ ๋ก๊ทธ์ธ ์ฌ๊ฐ์ด ๋ฑ๋ก๋์ด ์์ต๋๋ค Redux-Saga. ๊ทธ๋ฌ๋ yield์ฒซ ๋ฒ์งธ ์ค์ ๊ฐ์ ธ ๊ฐ๋ฉด ์ ํ 'LOGIN_REQUEST'์ด ์๋ ์์
์ด ์์ ์ ๋ฐ์ก ๋ ๋๊น์ง ์ฌ๊ฐ๋ฅผ ์ผ์ ์ค์งํฉ๋๋ค . ๊ทธ๋ ๊ฒ๋๋ฉด ์คํ์ด ๊ณ์๋ฉ๋๋ค.
๋ต๋ณ
ํ๋์ ๋น ๋ฅธ ๋ฉ๋ชจ. ์ ๋๋ ์ดํฐ๋ ์ทจ์ ๊ฐ๋ฅํ๊ณ ๋น๋๊ธฐ / ๋๊ธฐ ๊ฐ๋ฅํฉ๋๋ค. ๋ฐ๋ผ์ ์ง๋ฌธ์ ์๋ฅผ ๋ค์ด, ์ค์ ๋ก ๋ฌด์์ ์ ํ ํด์ผํ๋์ง ์ดํดํ์ง ๋ชปํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ ๋ณต์กํ ํ๋ฆ์ ๊ฒฝ์ฐ ๋๋๋ก ์์ฑ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค ๋ ๋์ ์๋ฃจ์ ์ด ์์ต๋๋ค.
๊ทธ๋์ ๋ ๋ค๋ฅธ ์์ด๋์ด๋ redux-thunk๊ฐ์๋ ๋ฐ์ ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด์ง๋ง ๋์๊ฒ๋ ์ฌ๊ฐ ๋ฐํด๊ฐ ๋ฌ๋ฆฐ ์์ ๊ฑฐ๋ฅผ ๋ฐ๋ช ํ๋ ค๊ณ ํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋ฌผ๋ก ๋ฐ์ ๊ธฐ๋ ํ ์คํธํ๊ธฐ๊ฐ ๋ ์ฝ์ต๋๋ค.