Redux 액션을 수행하면 Redux-Saga에서 디스패치해 Redux의 액션을 가로챈다. 중간에 가로챈 액션의 역할을 수행 후 다시 액션을 발행하여 데이터를 저장하거나 다른 이벤트를 수행시킨다.
일반 함수에서는 값을 하나만 반환 가능하지만 제너레이터를 사용하면 값을 순차적으로 반환할 수 있다. 함수의 흐름을 도중에 멈춰놓았다가 나중에 이어서 진행 할 수도 있다.
function*
를 사용해 함수를 만들고 함수를 호출하면 객체가 반환되는데 그것을 제너레이터
라고 한다.
function* generatorFunction() {
console.log('안녕하세요?');
yield 1;
console.log('제너레이터 함수');
yield 2;
console.log('function*');
yield 3;
return 4;
}
// 호출해 반환되는 제너레이터
const generator = generatorFunction()
호출만 한다고해서 코드가 실행되는 것이 아니다. generator.next()
를 호출해야 코드 실행이 된다. 첫번째 yield값을 반환하고 gernerator.next()를 호출하면 그다음 코드가 이어서 실행된다.
generator.next()
// '안녕하세요?'
// { value: 1, done: false }
generator.next()
// '제너레이터 함수'
// { value: 2, done: false }
generator.next()
// 'function*'
// { value: 3, done: false }
generator.next()
// { value: 4, done: true }
인자를 전달해 호출할 수도 있다.
function* sumGenerator() {
console.log('sumGenerator이 시작됐습니다.');
let a = yield;
console.log('a값을 받았습니다.');
let b = yield;
console.log('b값을 받았습니다.');
yield a + b;
}
const sum = sumGenerator
sum.next()
// 'sumGenerator이 시작됐습니다.';
// { value: undefined, done: false }
sum.next(1)
// 'a값을 받았습니다.'
// { value: undefined, done: false }
sum.next(6)
// 'b값을 받았습니다.'
// { value: 7, done: false }
call
특정 함수를 호출하고, 결과물이 반환 될 때까지 기다려준다.
put
새로운 액션을 디스패치
takeEvery
특정 액션 타입에 대하여 디스패치되는 모든 액션들을 처리하는 것
takeLatest
특정 액션 타입에 대하여 디스패치된 가장 마지막 액션만을 처리하는 함수
ex) 항상 마지막 버전의 데이터만 보여주어야 할 때
액션이 dispatch 될 때마다 새로운 task를 생성한다. 동시에 호출될 수 있고 실행순서가 보장되지 않는다. task가 동시에 중복으로 발생해도 문제가 없는 경우에 사용한다.
액션이 dispatch 됐을 때 이전에 이미 실행중인 task가 있으면 취소하고 새로운 task를 실행한다. 동일한 api 요청을 할 경우 가장 마지막 요청에 대한 데이터만 받아온다. 파라미터에 따라 결과값이 달라지는 GET 요청에 적합하다. 응답 데이터가 항상 같은 api 요청일 경우에 api요청이 중복으로 발생하게 되면 불필요한 딜레이가 생길 수 있다. 이런 경우에는 takeLeading
로 처리하는 것이 좋다.
처리순서가 중요하고 중복으로 호출되면 안되는 경우에 사용하는 것이 좋다. 어떤 변하지 않는 데이터를 받아오는 경우 이미 요청한 후에 다시 재요청할 필요가 없기 때문에 takeLatest
에 비해 빠르게 응답할 수 있다.
import { call, put, takeEvery } from 'redux-saga/effects'
export function* fetchData(action) {
try {
// 두번째 인자값을 전달해 api호출함수를 실행시킨다.
const data = yield call(Api.fetchUser, action.payload.url)
// 액션 "FETCH_SUCCEEDED"을 디스패치
yield put({type: "FETCH_SUCCEEDED", data})
} catch (error) {
// 액션 "FETCH_FAILED"을 디스패치
yield put({type: "FETCH_FAILED", error})
}
}
// 여러개의 fetchData 인스턴스를 동시에 시작한다.
function* watchFetchData() {
yield takeEvery('FETCH_REQUESTED', fetchData)
}
api호출할 때마다 매번 loading 상태값을 만드는 번거로움을 줄이기 위해 loading reducer를 만들어 data fetch할 때 적용할 수 있다. data fetch를 요청하는 액션 타입을 상태값으로 한다.
export const loadingSlice = createSlice({
name: 'loading',
initialState,
reducers: {
startLoading: (state, action: PayloadAction<string>) => {
state[action.payload] = true;
},
finishLoading: (state, action: PayloadAction<string>) => {
state[action.payload] = false;
},
},
});
data fetch하는 saga를 util함수로 만들어 재사용성을 높인다.
import { AxiosError } from 'axios';
import { call, put } from 'redux-saga/effects';
import { PayloadAction, PayloadActionCreator } from '@reduxjs/toolkit';
import { startLoading, finishLoading } from 'store/reducers/loading';
export const createFetchAction = <P>(
api: any,
requestActionType: string,
successAction: PayloadActionCreator<P>,
failureAction: PayloadActionCreator<AxiosError>,
) => {
return function* fetchApi(action: PayloadAction<P>) {
const payload = action.payload;
yield put(startLoading(requestActionType));
try {
const data: Promise<any> = yield call(api, payload);
yield put(successAction(data));
} catch (e) {
yield put(failureAction(e));
}
yield put(finishLoading(requestActionType));
};
};
data fetch 사가에서 호출할 api, 요청, 성공, 실패에 대한 액션을 인자로 넘겨 실행한다.
export default function* fetchDataSaga() {
yield takeEvery(
getLocationTableRequest.type,
createFetchAction(
getData, // api
getDataRequest.type,
getDataSuccess,
getDataFailure,
),
);
}
interface IResponse {
status: string;
message: string;
}
const createFetchAction = <P, T extends IResponse>(
api: any,
successAction: PayloadActionCreator<T>,
failureAction: PayloadActionCreator<Error | string>,
successFunc?: any,
failureFunc?: any,
) => {
return function* fetchApi(action: PayloadAction<P>) {
const { type, payload } = action;
yield put(startLoading(type));
try {
const data: T = yield call(api, payload);
const { status, message } = data;
// status === 'fail' && message가 존재할 때 failureAction 실행
if (status && status !== 'success'){
yield put(failureAction(message));
// failureAction 실행 후 실행할 함수(ex)error message alert, 다른 액션 실행)
if (failureFunc) {
yield call<typeof failureFunc>(failureFunc, message);
}
} else {
yield put(successAction(data));
// succeessAction 실행 후 실행할 함수(ex)success alert, 다른 액션 실행)
if (successFunc) {
yield call<typeof successFunc>(successFunc, data);
}
}
} catch (e) {
yield put(failureAction(e));
if (failureFunc) {
yield call<typeof failureFunc>(failureFunc, e);
}
}
yield put(finishLoading(type));
};
};
successFunc : api 요청 성공 후 실행 함수 (ex) 성공 alert or 요청 이후 다른 액션)
failureFunc : api 요청 실패 후 실행 함수 (ex) 실패 alert or 요청 이후 다른 액션)
api 요청은 성공했지만 response가 status: fail
이고 message
가 있을 경우 에러 처리를 할 수 있다.