npm i redux-saga
설치, 미들웨어에 집어넣는다
import createSagaMiddleware from 'redux-saga';
(...)
import rootSaga from '../sagas';
const configureStore = () => {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware]
(...)
store.sagaTesk = sagaMiddleware.run(rootSaga);
// rootSaga의 task를 store 객체에 넣어준다
return store;
}
const Wrapper = createWrapper(configureStore, {
debug: process.env.NODE_ENV == 'development'
})
export default Wrapper;
npm i next-redux-saga
설치, withReduxSaga를 붙여줘야 한다.
import withReduxSaga from 'next-redux-saga';
(...)
export default Wrapper.withRedux(withReduxSaga(App));
import { all, fork } from 'redux-saga/effects';
import userSaga from './user';
import postSaga from './post';
export default function* rootSaga() {
yield all([
fork(postSaga),
fork(userSaga)
])
}
put
: dispatch
의 saga버전이라고 생각하면 된다. Action을 dispatch
all
: 배열을 받아서, 배열에 들어있는 것들을 한번에 실행한다.
call
: 동기적으로 함수를 호출한다.
fork
: 비동기적으로 함수를 호출한다.
call 안에서 함수 표현방식은 일반적인 방법과는 조금 다르다
call(logInAPI, action.data, 'a','b','c')
는
logInAPI(action.data,a,b,c)
와 같다fork도 마찬가지
take
: 첫번째 인자(Action
)가 실행될 때 까지 기다린다,
두번째 인자가 있다면 Action
실행시 2번째 인자 Function
를 실행한다.
takeEvery
: 반복해서 take
한다. 순간적으로 계속 누르면 계속 take된다.
takeLatest
: 대기중인 task가 있다면 응답을 취소하고 마지막것만 take
해준다. backend에는 기록이 남아있으니 주의
throttle
: 3번째 인자로 밀리초(숫자)를 받음, 그 시간 안에 한번만 take
가능하다
delay
: setTimeout의 역할을 한다. 인자로 밀리초를 받음
const onSubmitLogin = useCallback(() => {
console.log(id,password)
dispatch(LogInRequestAction(id, password))
// 1. id, password를 적고 로그인 시도시 LogInRequestAction을 디스패치
// 2. reducer/user의 함수 참조
}, [id, password])
<FormWrap onFinish={onSubmitLogin}>
<div>
<label htmlFor="user-id">아이디</label>
<br/>
<Input name="user-id" value={id} onChange={onChangeId} required />
</div>
<div>
<label htmlFor="user-password">비밀번호</label>
<br/>
<Input name="user-password" value={password} onChange={onChangePassword} required />
</div>
<ButtonWrap>
<Button type="primary" htmlType="submit" loading={isLoggingIn}>Login</Button>
<Link href="/signup"><Button type="dashed">회원 가입</Button></Link>
</ButtonWrap>
</FormWrap>
로그인 버튼을 누르면 input의 value들(id,password)를 dispatch하는 onSubmitLogin
함수를 실행시킨다. 함수 안의 LogInRequestAction
은 이렇게 생겼다.
export const LogInRequestAction = data => {
return {
type: LOG_IN_REQUEST,
data
}
}
LOG_IN_REQUEST
가 dispatch
되어 saga에서 감지하게 된다
감지하는 saga부분은 이렇게 생겼다.
function* watchLogin() {
yield takeLatest(LOG_IN_REQUEST, login);
}
export default function* userSaga() {
yield all([
fork(watchLogin)
])
}
watchLogin()는 LOG_IN_REQUEST
를 감지하게되면 reducer의 이벤트 리스너
와 login
제너레이터함수를 실행한다.
case LOG_IN_REQUEST:
console.log('reducer login')
return { // LOG_IN ACTION이 감지되면 아래의 행동을 취한다.
...state, // 객체의 불변성을 유지하며 안의 값만 바꾸기 위함
isLoggingIn: true,
}
case LOG_IN_SUCCESS:
return {
...state,
isLoggingIn: false,
logInDone: true,
me: { ...action.data, nickname: 'mememe' }
}
case LOG_IN_FAILURE:
return {
...state,
isLoggingIn: false,
}
function* login(action) {
try {
console.log('saga login')
yield delay(2000)
yield put({
type: LOG_IN_SUCCESS,
data: action.data,
})
} catch (err) {
console.log(err)
yield put({
type: LOG_IN_FAILURE,
data: err.response.data
})
}
}
순서는 이렇다.
LOG_IN_REQUEST
가 dispatch되면 isLoggingIn을 true로
바꾼다dispatch
된다. 중간에 실패하면 catch(err) 부분이 dispatch
된다.LOG_IN_SUCCESS
또는 LOG_IN_FAILURE
의 이벤트리스너가 실행된다