Redux-saga의 필요성과 맛보기
로그인 버튼 클릭하는 순간 리덕스 state가 변경된다.
리덕스의 문제는 모든 것이 동기로 일어난다는 점
예를들면 아래가 실행시에
-> 서버쪽에 데이터 전달
-> 서버가 로그인 성공이라는 응답을 보내줌
-> 그걸 다시 받아서 로그인!
{
type: LOG_IN,
data: {
id: 'dadong',
password: '1234'
}
}
하지만 리덕스에서는 동기적으로 데이터를 바꾸는 것 밖에 못함
=> 리덕스로 dispatch해버리면 바로바로 실행되기 때문에 특정 시간, 또는 특정 동작 이후에 액션을 실행할 수가 없음.
그 중간에 서버에 데이터를 보냈다가, 응답을 보내주는 과정이 필요함.
이걸 리덕스가 못함
그래서 redux-saga를 씀! => 리덕스 기능을 확장할 때는 미들웨어를 씀.
리덕스 요청 사이사이에 비동기 요청을 보낼 수 있도록 하는 것
리덕스는 동기적인 동작만 함
이 사이에 비동기적인 동작을 넣으려면 리덕스 사가를 써야 함
예시)
내가 signUp 액션을 했다. 그리고 10초 뒤에 signUpSuccess라는 액션을 구현하고 싶다?
=> 리덕스는 간단한 타이머조차 못함. 액션을 바로바로 실행하는 것.
=> 이것을 보완해주는 것이 redux-saga이가.
=> 사가는 익숙하지 않은 문법이다.
우선 설치
npm i redux-saga
제너레이터는 함수 실행을 중간에 멈출 수 있고 원할 때 재개할 수 있어서 편하다.
마치 사람들이 *이 붙으니깐 오타인 줄 앎
하지만 제너레이터다!
=> 무한의 개념과 비동기에서 많이 사용함
function* generator() {
}
리듀서처럼 사가도 분리한 다음에 합쳐준다.
root.js (root saga)
user saga와 post saga를 만들어 준다.
takeLatest가 LOG_IN 액션이 dispatch 되길 기다려서
dispatch될 때 login 제너레이터를 호출합니다.
sagas₩user.js
로그인 동작할 때 3가지 액션이 있고 이걸 saga가 비동기로 이어주는 것
사가가 로그인
// reducers₩user.js
export const LOG_IN_SUCCESS = 'LOG_IN_SUCCESS'; // 액션의 이름
export const LOG_IN_FAILURE = 'LOG_IN_FAILURE'; // 액션의 이름
export const LOG_IN = 'LOG_IN'; //액션의 이름
REDUX-SAGA 사용법
import {all, fork, takeLatest, call, put} from 'redux-saga/effects';
import {LOG_IN, LOG_IN_SUCCESS, LOG_IN_FAILURE} from '../reducers/user';
function loginAPI() {
//서버에 요청을 보내는 부분
}
// 2. LOG_IN 요청이 들어오면 login 함수를 실행함
function* login(){
try {
yield call(loginAPI); //서버에 요청을 보내
yield put(action:{ // put은 dispatch와 동일
type: LOG_IN_SUCCESS, // 로그인이 성공했으면 다음줄이 실행됨 => LOG_IN_SUCCESS 실행
});
} catch (e) { //call(loginAPI) 실패하면 LOG_IN_FAILURE가 실행됨
console.error(e);
yield put(action:{
type: LOG_IN_FAILURE,
});
}
}
// 1. 사가가 로그인이라는 액션이 들어오는지 기다림 LOG_IN이 action!
function* watchLogin() {
yield takeLatest(LOG_IN, login) //LOG_IN 액션이 실행되면 loing이 실행됨
}
export default function* userSaga() {
yield all(effects: [
fork(watchLogin),
]);
}
리덕스에서 LOG_IN이 실행되면 REDUX-SAGA에서 비동기동작을 실행함
사가 미들웨어 리덕스에 연결하기
sagaMiddleware는 별도의 파일로 만들면 안됨
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { composeWithDevTools } from 'redux-devtools-extension';
import { createBrowserHistory } from 'history';
import { routerMiddleware } from 'connected-react-router';
import rootReducer from 'store/reducers/rootReducer';
import rootSaga from 'store/sagas/rootSaga';
export const history = createBrowserHistory();
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer(history),
compose(
applyMiddleware(routerMiddleware(history), sagaMiddleware),
),
);
sagaMiddleware.run(rootSaga);
export default store;
** 실제 서비스냐 아니냐 process.env.NODE_ENV === 'production' 이느냐에 따라서 조건문으로
__REDUX_DEVTOOLS_EXTENSION__ 추가 및 제거 가능
제너레이터
함수를 원할 때 멈췄다가 재개할 수 있음
사용자가 원할 때 중단점을 만들어 멈췄다가 재개(next())를 할 수 있다.
제너레이터는 next()를 붙여줘야 실행됨!
yield가 중단점임!
function* generator() {
console.log(1);
console.log(2);
yield;
console.log(3);
}
const gen = generator();
gen.next();
// 1
// 2
// {value: undefined, done: false}
// 중단점 yield에서 멈춤
gen.next();
// 3
// {value: undefined, done: true}
// 실행이 완료되면 done 이 true가 됨
원하는 지점에 yield를 넣으면 함수를 중단 시킬 수 있다.
function* generator() {
console.log(1);
console.log(2);
yield 5;
console.log(3);
}
const gen = generator();
gen.next();
// 1
// 2
// {value: 5, done: false}
// 중단점 yield에서 멈춤
// yield 뒤에 값을 붙이면 next할때 값이 나온다.
gen.next();
// 3
// {value: undefined, done: true}
// 실행이 완료되면 done 이 true가 됨
yied를 여러개 놓아도 됨
뒤에 *을 붙여서 반복문으로 사용가능
function* generator() {
yield 1;
yield 2;
yield 3;
yield 4;
yield* '12345';
yield* [1,2,3,4]; // 위 4줄과 해당 코드와 똑같다.
// iterable: 반복 가능 값
}
const gen = generator();
gen.next();
gen.next();
gen.next();
gen.next();
gen.next();
gen.next();
gen.next();
.
.
.
결과
제너레이터가 왜 리덕스 사가에 쓰이게 되었나?
-async await 같은 역할을 할 수 있게 해놓음(나오기 전에)
yield 를 await이라고 보면 됨.
함수를 중간에 멈출 수 있고. 우리가 재개를 컨트롤 할 수 있다는게 강력한 기능임
generator! 비동기를 자유자재로 컨트롤할 수 있다는 말
그래서 리덕스 사가가 generator를 사용함
사가의 제너레이터 이해하기
import {all, fork, takeLatest, call, put, take} from 'redux-saga/effects';
import {LOG_IN, LOG_IN_SUCCESS, LOG_IN_FAILURE} from '../reducers/user';
const HELLO_SAGA = 'HELLO_SAGA';
function loginAPI() {
//서버에 요청을 보내는 부분
}
// 2. LOG_IN 요청이 들어오면 login 함수를 실행함
function* login(){
try {
yield call(loginAPI); //서버에 요청을 보내
yield put(action:{ // put은 dispatch와 동일
type: LOG_IN_SUCCESS, // 로그인이 성공했으면 다음줄이 실행됨 => LOG_IN_SUCCESS 실행
});
} catch (e) { //call(loginAPI) 실패하면 LOG_IN_FAILURE가 실행됨
console.error(e);
yield put(action:{
type: LOG_IN_FAILURE,
});
}
}
// 1. 사가가 로그인이라는 액션이 들어오는지 기다림 LOG_IN이 action!
function* watchLogin() {
yield takeLatest(LOG_IN, login) //LOG_IN 액션이 실행되면 loing이 실행됨
}
function* helloSaga() {
//take: 해당 액션이 dispatch되면 제너레이터를 next하는 이펙트
// yeild는 중단점이고
yield take('HELLO_SAGA'); //헬로사가라는 액션이 들어왔을때 재개됨
// 재개는 next해줘야하는데 그걸안쓰고 take를 쓰면 그 안에서 해줌
console.log('hello saga');
}
export default function* userSaga() {
yield all(effects: [
fork(watchLogin),
// 아무 제너레이터나 넣어보기
helloSaga(),
]);
}
HELLO_SAGA가 들어오면 재개된다는 뜻임
yied take(HELLO_SAGA);
원래 yield는 멈추는것
그 뒤에 take하면 (next가 중단된 것 실행시켜주는것) 그 액션이 실행되면 이 함수가 재개됨
컴포넌트로 직접 dispatch 시켜주면 실행됨.
같은 액션을 사가가 여러번 listening하게 해주고 싶으면
while(true) {
yield take(HELLO_SAGA);
} 로 해주면 됨
이게 없다면 한번 이벤트 리스닝하고 함수 자체가 끝나버림.
무한이 -> HELLO_SAGA라는 액션이 들어오는 것을 대기하는 것.
all은 여러 이펙트를 동시에 실행할 수 있게 합니다.
// 사용자와 관련된 액션이 많을 때 all로 묶어서 모든 액션을 다 넣어준다.
export default function* userSaga() {
yield all( effects: [
watchHello(),
watchLogin(),
watchSignUp(),
]);
}
put은 사가의 redux의 dispatch와 같다!
LOG_IN 액션이 실행될 때까지 멈춰있다가. 실행되면 재개하고.
put으로 dispatch!
원래 yield는 중단점
그런데 take를 이용하면 그 LOG_IN이라는 Action이 실행될 때까지만 중단한다는 것.
즉, yield take(LOG_IN) 은 LOG_IN 액션이 들어올때까지 중단한다는 것.
function* watchLogin() {
yield take(LOG_IN);
yield put(action: {
type: LOG_IN_SUCCESS,
});
}
export default function* userSaga() {
yield all(effects:[
watchLogin(),
watchSignUp(),
]);
}
사가에서 반복문 제어하기!
=> 한번 실행하고 끝내는게 아니라 계속 그 액션이 take될 때마다 어떤 액션을 실행하고 싶으면 반복문을 넣어야함
근데 take가 반복문으로 안들어가있으면 한번 yield take(LOG_IN) 되고나서
put하고 나면 끝임
그래서! while(ture)로 감싸서 계속 LOG_IN을 take하는지 볼 수 있도록 하면 됨!
ex) 두번째 로그인을 실행했을 때 다음 것이 실행이 안될 수 있기에 while(true)로 감쌈
function* watchLogin() {
while (true) {
yield take(LOG_IN);
yield put(action: {
type: LOG_IN_SUCCESS,
});
}
}
export default function* userSaga() {
yield all(effects: [
watchLogin(),
watchSignUp(),
]);
}
'[React] Front-End' 카테고리의 다른 글
[FE] 이상적인 컴포넌트란? (0) | 2021.07.21 |
---|---|
[React] 버그 수정: onChange 함수 실행했다가 reRender 너무 많다고 뜰 때 (1) | 2021.07.20 |
[FE] Redux (3): Redux-devtools / immer (조현영 Redux vs Mobx 복습) (1) | 2021.07.18 |
[FE] Redux (2) : react-redux / reducer 분리 / Middleware / import vs require (0) | 2021.07.16 |
[FE] React 자동완성 : clg / rafce (0) | 2021.07.15 |