Redux
20210623 Redux06 : redux-saga, ES06 Generator 함수 문법, redux-saga 세팅&활용, redux-actions
Redux 06
redux-saga
- 미들웨어
- 제너레이터 객체를 만들어 내는 제너레이터 생성 함수를 이용
- 리덕스 사가 미들웨어를 설정 -> 자신이 만든 사가 함수 등록 -> 사가 미들웨어 실행 -> 등록된 사가함수를 실행할 액션을 디스패치 함
npm i redux-saga
- redux-saga by 밸로퍼트
- 아래 부터는 밸로퍼트님의 redux-saga를 참고하여 작성 하였습니다.
- redux-thunk가 하지 못하는 일을 redux-saga가 할수 있게 해줌
- 비동기 작업을 할 때 기존 요청 취소 처리
- 특정 액션 발생시 다른 액션이 디스패치 되게처리 또는 JS 코드 실행 처리
- API 요청이 실패 했을 때 재요청 작업 가능
- 등의 까다로운 비동기 작업들에서 사용
Generator 함수
- Generator 함수의 경우, 여러개의 return을 가지며 여러번에 걸쳐 return이 가능함
- 순차적 반환, 함수 흐름중 정지 또는 그 자리에서 다시 진행 가능
function*
키워드 사용- 제너레이터 : 제너레이터 함수 호출시 반환 되는 객체
- 제너레이터의
next()
함수를 통해서 코드가 실행되고,yield
를 한값을 반환하고, 정지함- 이어서 다시 실행 시킬려면
next()
를 다시 사용하면 됨
- 이어서 다시 실행 시킬려면
- 또한
next()
에 인자를 넣어 사용하면, 코드가 실행 될때 마다 필요한 인자를 활용해서 코드를 실행 시킬 수 있음 next()
에서 받은 인자는yield
로 들어가고, 실행이 정지됨
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이 시작됐습니다.
sum.next(1);
// a 값을 받았습니다.
sum.next(2);
// b 값을 받았습니다.
// {value: 3, done: false}
Generator로 액션 모니터링 하기
- while true를 넣음으로서 계속 반복하면서 정지하고 있는 형태가 됨
function* watchGenerator() {
console.log("모니터링 시작!");
while (true) {
const action = yield;
if (action.type === "HELLO") {
console.log("안녕하세요?");
}
if (action.type === "BYE") {
console.log("안녕히가세요.");
}
}
}
const watch = watchGenerator();
watch.next();
// 모니터링 시작!
watch.next({ type: "HELLO" });
// 안녕하세요?
watch.next({ type: "BYE" });
// 안녕히가세요.
redux-saga 세팅 및 활용
redux-saga middleware setting
- applyMiddleware에
createSagaMiddleware()
로 만든 객체인, sagaMiddleware를 추가함 - sagaMiddleware 객체에 있는
run()
함수에 Saga 함수를 모은rootSaga
를 넣어 실행
import { applyMiddleware, createStore } from "redux";
import reducer from "./modules/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import history from "../history";
import { routerMiddleware } from "connected-react-router";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./modules/rootSaga";
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
composeWithDevTools(
applyMiddleware(
thunk.withExtraArgument({ history }),
promise,
routerMiddleware(history),
sagaMiddleware
)
)
);
sagaMiddleware.run(rootSaga);
export default store;
redux-saga 함수 만들기
- saga의 경우 Watcher와 Worker로 구성되어 있음
- redux-saga by javarouka
- redux-saga by sustainable-dev
- redux-saga by mskim
- Wathcher
- saga 함수의 경우 비동기 작업 함수와 액션 타입을 연결해줌으로 서
- action의 상태를 subscribe 하는 역할을 함
- Worker
- 실제 작업을 수행 하는 부분
- saga-effect
- 미들웨어에 의해 수행되는 명령을 담고 있는 js 객체
- 모든 effect 들은 반드시 yield와 함께 사용해야 함
- 블럭 : 해당 코드가 다르 조건으로 블럭이 해지되기 전에는 다음 라인 코드가 실행 되지 않음
put
: 특정 Action을 middleware에서 dispatch 하게 하는 역할 (블럭X)call
: 해당 함수 작업에 대해서 middleware에서 실행시키게 함- 새로운 하위 saga 태스크를 생성하는 effect, 주로 promise 등의 실행, Ajax call (블럭O), resolve가 되어야 넘어감
all
: 제너레이터 함수를 배열의 형태로 인자로 넣으면, 제너레이터 함수들이 병행적으로 동시 실행 -> resolve 될 때 까지 기다림 (Promise.all 과 비슷)take
: 특정 액션 감시 용도 (블럭O)- fork : 새로운 하위 saga 태스크를 생성하는 effect (블럭X)
- select : redux state에서 특정 상태를 가져올 때 사용하는 effect (블럭O)
takeEvery
: 들어오는 모든 액션에 대해 특정 작업 처리(특정 액션과 수행 함수를 연결)
import axios from "axios";
import { push } from "connected-react-router";
import { call, delay, put, takeEvery } from "redux-saga/effects";
// redux-saga
// redux-saga ActionCreator
const GET_USERS_SAGA_START = "GET_USERS_SAGA_START";
export function getUsersSagaStart() {
return {
type: GET_USERS_SAGA_START,
};
}
// redux-saga 비동기 작업 함수 (Worker)
function* getUsersSaga(action) {
try {
yield put(getUsersStart());
yield delay(2000); // 블록 O
const res = yield call(axios.get, "https://api.github.com/users"); // 블록 O
yield put(getUsersSuccess(res.data));
yield put(push("/"));
} catch (error) {
yield put(getUsersFail(error));
}
}
// Watcher (매번 사용하기 위해서, 태스크를 쌓아줌)
export function* usersSaga() {
yield takeEvery(GET_USERS_SAGA_START, getUsersSaga);
// 어떤 액션 타입에 의해서 어떤 사가 작업 함수가 발동할지 적음
}
rootSaga 만들기
all
: 제너레이터 함수들이 병행적으로 동시 실행
import { all } from "redux-saga/effects";
import { usersSaga } from "./users";
export default function* rootSaga() {
// saga를 모아서 하나로 시작하게 하는 부분
yield all([usersSaga()]);
}
활용하기
- 해당 작업 함수를 dispatch해서 작업 요청
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersSagaStart } from "../redux/modules/users";
export default function UserListContainer() {
const users = useSelector((state) => state.users.data);
const dispatch = useDispatch();
const getUsers = useCallback(() => {
dispatch(getUsersSagaStart());
}, [dispatch]);
return <UserList users={users} getUsers={getUsers} />;
}
redux-actions
- ducks pattern을 쉽게 구현하게 도와주는 패키지
- redux-actions Official Site
npm i redux-actions
createAction 분석하기
import { createAction } from "redux-actions";
console.log(createAction("HELLO"));
// HELLO 함수가 return -> f HELLO
console.log(createAction("HELLO")());
// HELLO type의 Action -> {type: 'HELLO'}
console.log(createAction("HELLO")("안녕하세요"));
// HELLO type, payload가 안녕하세요 인 Action -> {type: 'HELLO', payload: '안녕하세요'}
redux-actions를 통해서 actionCreator와 reducer 만들기
creatActions()
를 통해서 type을 넣어 Action을 생성하고, ActionCreator 함수명은 알아서 만들어짐- 마지막에
{prefix: ""}
옵션을 넣으면, 자동으로 type에 앞에 해당 prefix를 붙여 ducks pattern에 맞는 type이름으로 만들어 줌 - 구조 분해 할당 방식으로 해당 ActionCreator를 받아와 밖에서 자유롭게 사용 가능
- reducer의 경우에는 ,
handleActions()
를 활용하여 변경할 해당 state 값을 변경- 첫번째 인자는 type에 따른 return 값 함수
- 두번째 인자는 initialState 로 초기값
- 세번째 인자는 들어오는 type에서 prefix를 거두어 내고, 로직 적용하기 위함으로 어떤 prefix인지를 넣음
// filter.js
import { createActions, handleActions } from "redux-actions";
// action Creator
export const { showAll, showComplete } = createActions(
"SHOW_ALL",
"SHOW_COMPLETE",
{
prefix: "redux-start/filter",
}
);
// Initial Data structure
const initialState = "ALL";
// reducer
const reducer = handleActions(
{
SHOW_ALL: (state, action) => "ALL",
SHOW_COMPLETE: () => "COMPLETE",
},
initialState,
{ prefix: "redux-start/filter" }
);
export default reducer;