[React] Front-End

[FE] Redux (4): Redux-Saga 의 필요성 / 사용법 (조현영 Redux vs Mobx 복습)

ddgoori 2021. 7. 18. 22:38

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(),
    ]);
}