[React] Front-End

[FE] Redux (5): Redux-Saga/ takeEvery / takeLastest (조현영 Redux vs Mobx 복습)

ddgoori 2021. 7. 21. 19:35

takeEvery, takeLatest

 

99%의 경우 Whilte(true) 를 뺄 이유가 없다.

항상 반복이 되어야 하는 동작이다.

ex) 한번만 로그인하고 한번만 로그아웃 하지는 않음.

다른 유저가 접속했을 때 로그인후 로그아웃하고 다시 로그인 로그아웃을 해야하기 때문에

한번만 실행되는 액션은 없음!

 

 

takeEvery

 

그래서 takeEvery라는 것을 지원함.

 

기존:

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

fucntion* watchHello() {
    while(true) {
    // 액션을 테이크하는 부분
        yield take(HELLO_SAGA);
    // 실제 동작하는 부분 같이 있는 것이 문제
        console.log(1);
        console.log(2);
        console.log(3);
        console.log(4);
        console.log(5);
    }
}

 

변경 후:

 

takeEvery와 제너레이터함수 function*()안에 넣기

=> HELLO_SAGA가 액션이 take되면 이후 실행되는 코드 부분이 구분이 됨!

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

function* watchHello() {
    yield takeEvery(HELLO_SAGA, function*(){
        console.log(1);
        console.log(2);
        console.log(3);
        console.log(4);
    });
}

 

HELLO_SAGA를 6번 dispatch하면, 실행부가 6번 반복됨.

 

또다른 예제)

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

function* watchHello() {
    yield takeEvery(HELLO_SAGA, function*(){
 	yield put({
        type: 'BYE_SAGA'
        });
    });
}

 

 

takeLatest

 

겉보기에는 takeEvery랑 비슷함.

근데 아래처럼 yield delay(1000); 비동기 코드가 들어갔다면

 

아래 코드가 takeEvery이면 dispatch 6번 => HELLO_SAGA 6번 / BYE_SAGA 6번 실행됨

takeLastest로 dispatch 6번 => HELLO_SAGA 6번 / BYE_SAGA 는 1번 실행됨

 

ex) 동시에 요청하면 마지막 것만 받겠다. 예를 들면 로그인 10번 클릭하면, 마지막 1번만 가도록함

takeLastest: 이전 요청이 끝나지 않은게 있다면 이전 요청을 취소합니다.

saga가 effect로 제너레이트를 이용해 제어해주는 것이다!

 

takeEvery takeLastest 무엇을 그럼 해야할까요?

=> 2번 동시에 실행했을 때 두 번 다 유효하게 해줄 것인가, 아니면 마지막것만 유효하게 해줄것인가? 로 판단하면 됨!

=> ex. 로그인 ( takeLastest) / 버튼 여러번 눌러서 카운트 올리는 거다 (takeEvery)

=> 여러번 클릭하는게 실수다! 그럼 takeLastest / 여러번 클릭하는게 다 동작해야한다 takeEvery

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

function* watchHello() {
    yield takeLastest(HELLO_SAGA, function*(){
    	 yield delay(1000);
 	yield put({
        type: 'BYE_SAGA'
        });
    });
}

 

takeLastest의 경우 결과값 : 

 

액션 분리!

 

function* watchHello() {
    yield takeLastest(HELLO_SAGA, function*(){
    	yield delay(1000);
 	yield put({
        type: 'BYE_SAGA'
        });
    });
}

 

function* hello() {
    yield delay(1000);
    yield put({
        type: 'BYE_SAGA'
        });
    });
}

function* watchHello() {
    yield takeLastest(HELLO_SAGA, hello);
}

 

fork, call, 사가 총정리

 

- fork call 둘 다 함수를 실행해주는 것임

- 사가에서는 함수실행도 effect로 많이 처리함

 

fork: 비동기처리 => 서버에 응답이 오는 말든 바로 다음 것 실행

call: 동기처리 => 서버 요청에서 응답이 와야 다음 것 실행

 

 

 

fork

 

사용전

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

export default function* userSaga() {
    yield all([
        watchLogin(),
        watchHello(),
    ]);
}

 

사용후

 

all 둘다 watch하고 싶을 때 사용

 

fork를 붙이는 이유: watchLogin / watchHello는 순서가 없음.

순서가 없기 때문에 비동기로 처리해도 됨~

 

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

export default function* userSaga() {
    yield all([
       fork(watchLogin),
       fork(watchHello),
    ]);
}

 

 

 

 

call

 

call: 동기처리 => 서버 요청에서 응답이 와야 다음 것 실행

순서를 지켜서 실행해야하는 것은 무조건 call!

 

 

사용전

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

function loginAPI() {
	//서버에 요청을 보내는 부분
}

function* login() {
    try{
        yield delay(100);
        yield put({
            type: LOG_IN_SUCCESS,
        });
    } catch (e) {
        console.error(e);
        yield put({
            type: LOG_IN_FAILURE,
        });
    }
}

 

사용후

 

call => loginAPI 요청이 끝나야 다음으로 실행됨. 

yield call(loginAPI) 코드 실행이 에러가나면 바로 catch함수로 넘어감.

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

function loginAPI() {
	//서버에 요청을 보내는 부분
}

function* login() {
    try{
        yield call(loginAPI);
        yield put({
            type: LOG_IN_SUCCESS,
        });
    } catch (e) {
        console.error(e);
        yield put({
            type: LOG_IN_FAILURE,
        });
    }
}

 

또 다른 예제

 

call로 logger를 하면, 10초 기다린 후에 call(logInAPI)가 실행됨.

근데 fork를 하면 비동기로 완료되든 10초 걸리든 말든~ 알아서 실행해~

그러고 바로 

call(logInAPI)가 실행되도록 한다.

=> 이 다음 성공하면 LOG_IN_SUCCESS

=> 실패하면 LOG_IN_FAILURE 로 넘어간다.

import { all, fork, takeLastest, takeEvery, call, put, take, delay } from 'redux=saga/effects';

function loginAPI() {
	//서버에 요청을 보내는 부분
}

function* login() {
    try{
        yield fork(logger); // logger는 내 기록을 로깅하는 함수 10초걸림
        yield call(loginAPI);
        yield put({
            type: LOG_IN_SUCCESS,
        });
    } catch (e) {
        console.error(e);
        yield put({
            type: LOG_IN_FAILURE,
        });
    }
}

 

 

Redux-Saga

 

나는 로그인 하면서 서버에 요청도 보내고, 서버의 요청 결과도 다시 액션으로 받고싶다!

그런 경우에 redux만으로 안되고, 1)비동기나 2)타이머나 3)액션을 연달아서 실행할 수 있게 해주는 redux-saga를 씀

=> 이때 generator함수를 씀. yield라는 중단점만 잘 붙여주면된다!

 

 

 

사가패턴

 

비동기요청은 아래와 같은 request / success / failure 로 네이밍됨

saga를 쓰는구나~ 

 

동기 요청은

그냥 바로 아래처럼 동기이면 Redux만 사용하면 됨

 

 

보통 Request 할 때 로딩창 돌아가는 것 isLoading: True

 

성공하면 isLoading: false로 처리

 

+ try catch는 에러가나도 안죽도록 보호하는 것

 

yield call(loginAPI)가 성공할 수도 있고 실패할 수 있다! 실패할 경우 catch에 걸림.

 

 

그래서 redux-saga 패턴은?

saga₩user.js

 

1. 등록을 해놓고

2. takeEvery 인지 takeLatest인지 결정하고

 

3. 실제로 동작할 것은 아래처럼 적어주기

 

Q.

동기 / 비동기를 꼭 saga를 써서 제어해야하나요?

컴포넌트 자체에서 async와 await을 주어도 비동기처리를 할 수 있지 않나요?

 

A. 

사가를 쓰면 컴포넌트에서 dispatch할 때 계속 비동기 처리를 중복적인 코드를 써서 처리해줘야하는데.

사가를 사용하면 컴포넌트에서 Dispatch만 해줘도 알아서 비동기 처리가 된다.

 

비동기를 관리하는 하나의 시스템으로 saga를 사용하는 것이다.

 

 

사가 쓰기 전 코드:

 

사가를 쓴 후 코드: