Redux-Saga

준호·2020년 8월 30일
1

Redux-Saga

store/configureStore.js

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;
pages/_app.js

npm i next-redux-saga 설치, withReduxSaga를 붙여줘야 한다.

import withReduxSaga from 'next-redux-saga';
(...)
export default Wrapper.withRedux(withReduxSaga(App));
sagas/index.js
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)
  ])
}
redux-saga/effects 소개

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의 역할을 한다. 인자로 밀리초를 받음

redux-saga의 실행 순서

component/LoginForm.js
 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은 이렇게 생겼다.

reducer/user.js
export const LogInRequestAction = data => {
  return {
    type: LOG_IN_REQUEST,
    data
  }
} 

LOG_IN_REQUESTdispatch되어 saga에서 감지하게 된다

감지하는 saga부분은 이렇게 생겼다.

sagas/users.js
function* watchLogin() {
  yield takeLatest(LOG_IN_REQUEST, login);
}

export default function* userSaga() {
  yield all([
    fork(watchLogin)
  ])
}

watchLogin()는 LOG_IN_REQUEST를 감지하게되면 reducer의 이벤트 리스너login 제너레이터함수를 실행한다.

reducer/user.js
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,
}
sagas/user.js
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
    })
  }
}

순서는 이렇다.

  1. reducer에서 LOG_IN_REQUEST 가 dispatch되면 isLoggingIn을 true로 바꾼다
  2. saga에서 try {} 부분이 dispatch된다. 중간에 실패하면 catch(err) 부분이 dispatch된다.
  3. reducer에서 LOG_IN_SUCCESS 또는 LOG_IN_FAILURE의 이벤트리스너가 실행된다
profile
빠르게 발전중인 프론트엔드 개발자

0개의 댓글