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의 이벤트리스너가 실행된다