npm i redux-saga
또는
npm i next-redux-saga
//src > redux > store.js
import history from "./history";
import { routerMiddleware } from 'connected-react-router';
import createSagaMiddleware from "redux-saga";
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
composeWithDevTools(
applyMiddleware(
thunk.withExtraArgument({ history }), promise,
routerMiddleware(history),
sagaMiddleware
)
)
);
// users.js
import { put } from "redux-saga/effects";
const GET_USERS_SAGA_START = "GET_USERS_SAGA_START";
function* getUsersSaga(action) {
try {
yield put(getUsersStart())
yield delay(2000);
const res = yield call(axios.get, "")
yield put(getUsersSuccess(res.data));
yield put(push("/")); // push는 connected-react-router의 모듈
} catch(error) {
yield put(getUsersFail(error));
}
}
export function getUsersSagaStart() {
return {
type: GET_USERS_SAGA_START
};
}
// 만든 saga함수를 saga-middleware에 등록시켜야 하는데,
// users라는 리듀서 안에서 만들어진 사가함수를 한 곳(rootSaga)에 모아서 미들웨어에 전달
export function* usersSaga() {
yield takeEvery(GET_USERS_SAGA_START, getUsersSaga)
}
// src > redux > modules > rootSaga.js
export default function* rootSaga() {
yield all([usersSaga()]);
}
// UserListContainer.jsx
import { getUsersSagaStart } from "../redux/modules/users";
export default function UserListContainer() {
const users = useSelector((state) => stte.users.data);
const dispatch = useDispatch();
const getUsers = useCallback(() => {
dispatch(getUsersSagaStart());
}, [dispatch]);
return <UserList users={users} getUsers={getUsers} />;
// store.js
const store = createStore(
... // 생략(궁금하면 위쪽 코드 참고)
);
// saga함수를 saga-middleware에 등록
// store가 생긴 후에 설정해줘야 해서 아래쪽에 코드 추가
sagaMiddleware.run(rootSaga);
delay: 설정된 시간 이후에 resolve하는 Promise객체를 리턴
ex) delay(1000)
put: 특정 액션을 dispatch하도록 한다
ex) put({type: 'INCREMENT'})
takeEvery: 들어오는 모든 액션에 대해 특정 작업을 처리해준다 (비동기적으로 동작, 동기적으로 동작시키려면
while(true) {yield take()}
를 사용하면 됨(yield는 일회용이라서 계속 사용하기 위해서 while(true)
라는 조건문 안에 넣어주기))
ex) takeEvery(INCREMENT_ASYNC(액션타입), increaseSaga(사가함수))
takeLatest: 기존에 진행중이던 작업이 있다면 취소처리하고, 가장 마지막으로 실행된 작업만 수행
ex) takeLatest(DECREASE_ASYNC, decreaseSaga)
프론트 쪽에서만 요청을 취소해주고, 백엔드쪽에는 요청이 간 것을 취소 못함 > 백엔드에는 이미 데이터들이 있기 때문에 새로고침을 하면, 취소한 요청들까지 화면에 보일 수 있음 > 백엔드의 데이터도 삭제해줘야 함 (ex)로그인버튼 두번 연속클릭 같은 경우
> throttle(액션타입, 사가함수, 시간(초단위)): 해당 시간동안은 해당 액션타입은 딱 한번만 실행할 수 있게 됨 (디바운싱과 비교)
call: 함수의 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수이다.
ex) call(delay, 1000) === delay(1000)
all: all함수를 사용해서 제너레이터 함수를 배열의 형태로 인자로 넣어주면, 제너레이터 함수들이 병행적으로 동시에 실행되고, 전부 resolve될 때까지 기다린다 (Promise.all과 비슷)
ex) yield all([testSaga1(), testSaga2()]) > testSaga1과 testSaga2가 동시에 실행되고, 모두 resolve될 때까지 기다린다
select: reducer에 있는 상태를 redux-saga에서 가져와서 사용할 수 있게 해준다
fork: call과 다르게 비동기적으로 작동한다(요청 안기다리고 바로 다음 것 실행)
take: 액션을 기다린다
takeLeading: 맨 첫번째 것만 실행(takeLatest와 반대)
npm i redux-actions
: ducks pattern을 쉽게 구현할 수 있도록 도와주는 라이브러리
// createAction("액션타입")("payload"): 액션타입 1개일 때 사용
import {createAction} from 'redux-actions';
createAction("액션타입")("payload")
// createActions("액션타입")("payload"): 액션타입 여러개일 때 사용
import {createActions} from 'redux-actions';
export const { 액션1생성함수명, 액션2생성함수명 } = createActions("액션타입1","액션타입2", { prefix: "파일명/리듀서명" });
//prefix부분: ducks pattern에서 타입만들때 앞에 붙히는 것 만들어주는 부분
const reducer = handleActions(
{
SHOW_ALL: (state, action) => "ALL",
SHOW_COMPLETE: () => "COMPLETE"
},
initialState,
{ prefix: "파일명/리듀서명" }
);