[React.js]리액트_redux-saga
2021. 8. 3. 00:52ㆍWeb_Programming/React
💻 redux-saga를 이용한 비동기 액션처리
비동기 액션을 처리하는 라이브러리
리덕스에서 액션 처리후, 비동기 처리를 통해 상태값을 변경시키고 싶을 경우 사용
- redux-thunk
- redux-observable (RxJS 패키지 기반)
- redux-saga
: 제너레이터 기반, 활용도가 높고/ 테스트 코드 작성에 쉬움.
💡 제너레이터
제너레이터는 function*와 같이 작성.
function* f1() {
console.log('f1-1');
yield 10;
console.log('f1-2');
yield 20;
console.log('f1-3');
return 'finished';
}
const gen = f1();
console.log(gen.next());
/*
실행구간
console.log('f1-1');
yield 10;
반환객체
{ value: 10, done: false }
*/
console.log(gen.next());
/*
실행구간
console.log('f1-2');
yield 20;
반환객체
{ value: 20, done: false }
*/
console.log(gen.next());
/*
실행구간
console.log('f1-3');
yield 30;
반환객체
{ value: 'finished', done: true }
*/
📍 next 함수 (제너레이터 객체 내의 함수)
제너레이터의 yield 구간을 기준으로 나뉘어 호출
value: yield 오른쪽 값 / done: 제너레이터 함수가 모두 완료되었는지 여부
iterator vs iterable
iterator( 반복자) | iterable(반복 가능) |
next 함수 사용가능 | Symbol.iteraor() 속성값으로 가짐 |
next 함수: value/done 속성값의 객체를 반환 | Symbol.iteraor() 호출시, iterator 반환 |
작업 종료시, done ==true |
👉 제너레이터는 iterator이면서 iterable입니다.
이로인해, for of문과 전개연산자 사용가능
function* f1() {
yield 10;
yield 20;
yield 30;
}
for (const v of f1()) {
console.log(v);
}
const arr = [...f1()];
console.log(arr); // [ 10, 20, 30 ]
제너레이터 예제
function* user() {
const userMessageList = ['Hi, I am keeper!', 'Nice to meet you!'];
for (const message of userMessageList) {
console.log('User: ', yield message);
}
}
function kepper() {
const gen = user();
const robotMessageList = ['', 'Hi, I am user!'];
for (const message of robotMessageList) {
console.log('Keeper: ', gen.next(message).value);
}
}
❗ 제너레이터 next에 인자로 값을 전달하면 전달된 인자는 yield의 반환값
- gen.next('')=> 로그: Keeper: Hi, I am keeper!
- gen.next('Hi, I am user!')=> 로그: User: Hi, I am User!
- 반복
리덕스 사가 함수 : user()
리덕스 사가 미들웨어 : keepper()
💡 리덕스 사가의 이펙트 함수
put : 액션 발생
call : 서버 API 호출
takeLeading (<-> takeLatest)
두번째 인자 함수가 아직 진행중에 있다면, 그 사이에 들어온 액션은 무시하고 처음에 들어온 액션을 우선으로 처리하도록 관리해줍니다.
import { all, call, put, takeLeading } from 'redux-saga/effects';
export function* fetchData(action) {
yield put(actions.setLoading(true));
yield put(actions.addLike(action.timeline.id, 1));
yield call(callApiLike);
yield put(actions.setLoading(false));
}
export default function* () {
yield all([
takeLeading(type.REQUEST_LIKE, fetchData)
// takeLeading(type.REQUEST_LIKE1, fetchData1)
// takeLeading(type.REQUEST_LIKE2, fetchData2)
// takeLeading(type.REQUEST_LIKE3, fetchData3)
]);
}
📍 take함수 반환객체
var takeReturn = take(types.REQUEST_LIKE);
var takeReturnLog = {
TAKE: {
pattern: 'timeline/REQUEST_LIKE'
}
}
📍 put 함수 반환객체
var putReturn = put(actions.setLoading(false));
var putReturnLog = {
PUT: {
channel: null,
action: {
type: 'timeline/SET_LOADING',
isLoading: false
}
}
}
📍 call 함수 반환객체
var callReturn = call(callApiLike);
var callReturnLog = {
CALL: {
context: null,
fn: callApiLike,
args: [],
}
}
이렇게 반환된 객체는 yield를 호출시 👉 사가 미들웨어에 전달
사가 미들웨어는 부수 효과 완료 후 👉 그 결과와 함께 실행흐름을 다시 제네레이터(fetchData) 쪽으로 전달
💡 리덕스 사가 미들웨어 추가하기
import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import { createSagaMiddleware } from 'redux-saga';
import { all } from 'redux-saga/effects';
const reducer = combineReducers({
timeline: timelineReducer,
friend: friendReducer,
})
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
composeEnhancers(
applyMiddleware(sagaMiddleware)
)
)
function* rootSaga() {
yield all([
timelineSaga(),
friendSaga()
]);
}
sagaMiddleware.run(rootSage);
export default store;
- 기존 리덕스 적용 코드에서 createSagaMiddleware() 를 이용하여 생성
- createStore에서, 미들웨어를 등록하는 composeEnhancers 함수를 입력, applyMiddleware 함수에 미들웨어 객체를 넣어줍니다.(applyMiddleware 여러개 입력가능)
- all 함수를 통해서 모든 사가를 추가시켜준 다음 사가 미들웨어를 실행
💡 다수 액션 처리
LOGIN액션과 LOGOUT액션 처리
function* loginFlow() {
while(true) {
// [로그인]
// 로그인 액션을 기다림
// 로그인 API 호출
// 로그인 정보를 저장
const { id, password } = yield take(types.LOGIN);
const userInfo = yield call(callApiLogin, id, password);
yield put(types.SET_USER_INFO, userInfo);
// [로그아웃]
// 로그아웃 액션을 기다림
// 로그아웃 API 호출
// 로그인 정보를 제거
yield take(types.LOGOUT);
yield call(callApiLogout, id);
yield put(types.SET_USER_INFO, null);
}
}
💡 리덕스 사가 예외 처리
try/catch문을 이용하여 예외 처리
import { all, call, put, takeLeading } from 'redux-saga/effects';
export function* fetchData(action) {
yield put(actions.setLoading(true));
yield put(actions.addLike(action.timeline.id, 1));
// 에러처리
try {
yield call(callApiLike);
} catch (error) {
// 에러객체 저장
// Like 원래상태로 되돌림
yield put(actions.setValue('error', error));
yield put(actions.addLike(action.timeline.id, 1));
}
yield put(actions.setLoading(false));
}
export default function* () {
yield all([
takeLeading(type.REQUEST_LIKE, fetchData)
]);
}
💡 디바운스
: 같은 함수가 여러번 연속해서 호출 될 때, 첫번째 또는 마지막 호출만 실행하는 기능
리덕사 사가의 디바운스 함수
일정 시간 내에 제너레이터 함수가 여러번 호출 되더라도 한번만 실행
REQUEST_LIKE 액션이 발생했을 때 바로 함수를 호출하지 않고 1초를 기다렸다가 더이상 액션이 발생하지 않으면 fetchData 함수를 실행
import { all, call, put, takeLeading } from 'redux-saga/effects';
export function* fetchData(action) {
yield put(actions.setLoading(true));
yield put(actions.addLike(action.timeline.id, 1));
try {
yield call(callApiLike);
} catch (error) {
yield put(actions.setValue('error', error));
yield put(actions.addLike(action.timeline.id, 1));
}
yield put(actions.setLoading(false));
}
export default function* () {
yield all([
debounce(1000, type.REQUEST_LIKE, fetchData)
]);
}
'Web_Programming > React' 카테고리의 다른 글
리액트 타입스크립트_개념&사용법 (0) | 2021.10.11 |
---|---|
[React]카카오 REST API 로그인_TS (0) | 2021.09.17 |
[React.js]리액트 react-redux, reselect라이브러리 (0) | 2021.07.30 |
[React.js] 리덕스(액션,미들웨어, 리듀서, 스토어) (0) | 2021.07.21 |
[React.js] 리액트의 useEffect 활용법 & 성능 최적화 방법 (0) | 2021.07.19 |