[React] 20. Redux-Saga( TOOLKIT - createSlice적용 )

dev·2020년 10월 29일
0

React

목록 보기
20/21

전 게시글에서 Redux-Saga를 사용하고, toolkit의 createAction과 createReducer을 사용하여 reducer부분을 좀더 간결하게 사용을 해보았습니다.
models/counter.js파일에 Redux-Saga와 Redux-Toolkit을 같이 사용하였습니다.
이번에는 createSlice를 사용하여 Saga와 Toolkit을 분리하여
Saga파일, Toolkit파일을 만들어 보겠습니다.

현재 진행하려는 예제 전체 코드는 [React] 19. Redux-Saga( TOOLKIT적용 )

을 보시면 되겠습니다. 해당 게시글 아래에 전체코드를 작성해놨습니다.

createSlice

createSlice는 기존에 했던 createReducer보다 더 간단하게 작성 할 수 있습니다.
createReducer는 기존 reducer에서 액션생성함수를 생략할 수 있게 해주는데 createSlice는 액션함수랑 초기값까지 reducer를 만들때 같이 만들어 버려서 코드량이 더 줄어들고 한번에 보기 쉬어지는 장점이 있습니다.

일단 saga 비동기방식을 사용하는 부분은 sagaCounter.js파일에
state 변경하는 건 sliceReducer.js파일을 만들었습니다.

sliceReducer.js

toolkitCounter.js에서 state변경하는 부분을 작성해보도록 하겠습니다. (increase, decrease, getNumberS, getNumberF, initialState관련)
일단 코드를 보겠습니다.

import { call, delay, put, takeEvery, takeLatest } from 'redux-saga/effects';
import * as API from '../api/posts';
import { createAction, createReducer, createSlice, createSelector } from "@reduxjs/toolkit"; //createSlice 추가
const name = "CSCount"
const initialState = {number: 1, error : "", errDesc: "", test : 10};
const reducers ={
    increase : (state, {payload : param}) => {
        return {number : state.number + param.number};
    },
    decrease : (state, {payload : param}) => {
        return {number : state.number - param.number};
    },
    getNumberS : (state, {payload : param}) => {
        debugger;
        state.number = param[0];
        state.error = false;
        state.errDesc = "";
    },
    getNumberF : (state, {payload : param}) => {
        debugger;
        state.number = 1;
        state.error = true;
        state.errDesc = param.error.message;
    }
}
export const slice = createSlice({
    name,
    initialState,
    reducers    
});
export const sName = slice.name;
export const sReducer = slice.reducer;
export const sAction = slice.actions;

일단 createSlice형식은

createSlice({
name:name,
state : state,
reducers : reducers
});

로 되어있습니다.
저는 createSlice의 name,state, reducers를 따로 변수에 담아서 사용하는 방식으로 했습니다.

createSlice를 사용하기 위해

import { createAction, createReducer, createSlice, createSelector } from "@reduxjs/toolkit";
로 createSlice를 추가하였습니다.

그리고 createSlice에 들어갈 name 과 state, reducers를 변수에 담았습니다.

const name = "CSCount"
사용한 이름을 name변수에 담고,
const initialState = {number: 1, error : "", errDesc: "", test : 10};
state값을 initialState 변수에 담고,
const reducers ={
    increase : (state, {payload : param}) => {
        return {number : state.number + param.number};
    },
    decrease : (state, {payload : param}) => {
        return {number : state.number - param.number};
    },
    getNumberS : (state, {payload : param}) => {
        debugger;
        state.number = param[0];
        state.error = false;
        state.errDesc = "";
    },
    getNumberF : (state, {payload : param}) => {
        debugger;
        state.number = 1;
        state.error = true;
        state.errDesc = param.error.message;
    }

}
리듀서를 reducers변수에 담았습니다.

여기서 reducers를 한번 보겠습니다.

{
	incresae : (state, payload) => {}
    decrease : (state, payload) => {}
    getNumberS : (state, payload) => {}
    getNumberF : (state, payload) => {}
}

incease, decrease, getNumberS, getNumberF는 리듀서의 key값으로
액션함수라고 생각하시면 됩니다.
해당 리듀서 key값인(액션함수) increase로 dispatch시키면 slice의 increase : () =>{} 로직을 타게됩니다.
그리고 {}안에서 return으로 state를 가지는데

기존 리듀서를 생각해보면 현재 state의 값을 바로 바꾸지않고 concat, assign등을 사용하여 복사하고 그 복사한 state를 리턴해줬는데, createSlice의 리듀서 안에서는 불변성을 직접 작업해주기 떄문에 바로 state에 접근이 가능합니다.

ex) state.number = payload.number 이렇게 바로 변경이 가능합니다.

다음은 createSlice를 만들도록하겠습니다.

export const slice = createSlice({
    name,
    initialState,
    reducers    
});
아까 위에서 만든 name, initialState,reducers를 넣어줍니다.

이렇게하면 sliceReducer가 만들어줬습니다.

다음으로는 비동기를 사용하는 sagaCounter.js를 보도록하겠습니다.

sagaCounter.js

toolkitCounter.js에 작성한 코드를 복사해 SagaCounter.js에 붙여놓고 실제 state를 변경하는 부분을 지우고 slice파일을 적용하여 작성했습니다.
(increase, decrease, getNumberS,getNumberF, initialState관련)

import { call, delay, put, takeEvery, takeLatest } from 'redux-saga/effects';
import * as API from '../api/posts';
import { createAction, createReducer } from "@reduxjs/toolkit";
import * as slice from "./sliceReducer";
// 액션 타입
const INCREASE_ASYNC = 'INCREASE_ASYNC';
const DECREASE_ASYNC = 'DECREASE_ASYNC';
const GET_NUMBER = 'GET_NUMBER'; // 요청 시작
// createAction으로 액션 생성 함수를 만들 수 있다.
export const increaseAsync = createAction(INCREASE_ASYNC,param => {return {"payload":param}});
export const decreaseAsync = createAction(DECREASE_ASYNC, param => { return {"payload": param}});
export const getNumber = createAction(GET_NUMBER);
// 제너레이터 함수
export function* counterSaga() {
  yield takeEvery(increaseAsync, increaseSaga);
  yield takeEvery(getNumber, numberSaga);
}
export function* counterSaga2() {
  yield takeLatest(decreaseAsync, decreaseSaga); //가장 마지막으로 디스패치된 DECREASE_ASYNC 액션만
}
function* numberSaga(){
  const {getNumberS, getNumberF} = slice.sAction;
  try{
    const number = yield call(API.getNumber);
    // call 을 사용하면 특정 함수를 호출하고, 결과물이 반환 될 때까지 기다려줄 수 있습니다.
    yield put(getNumberS(number));
  }
  catch(e){
    yield put(getNumberF({type : "GET_NUMBER_F",error:e, payload: 0}));
  }
}
function* increaseSaga(param) {
  const { increase, decrease } = slice.sAction;
  yield delay(1000); // 1초를 기다립니다.
  if(param.type === INCREASE_ASYNC){
    yield put(increase(param.payload));
  }
  else{
    yield put(decrease(param.payload));
  }
}
function* decreaseSaga(param) {
  const { increase, decrease } = slice.sAction;
  yield delay(1000);
  yield put(decrease(param.payload));
}

위에서 부터 보겠습니다.
일단 리듀서를 사용하기 위해 sliceReducer를 import해줍니다.

import * as slice from "./sliceReducer";

그리고 saga파일에서는 state를 변경하는 increase,decrease등 createAction함수가 제거 되었고, 초기state값 지정, Redcuer가 삭제되었습니다.

이부분에서 보실 부분은 제너레이터함수에서 sliceReducer에서 만든애들을 부르는겁니다.

  yield takeEvery(increaseAsync, increaseSaga);

부분에서 increaseAsync로 들어왔을때 increaseSaga()함수를 호출합니다.

increaseSaga()함수를 보도록하겠습니다.

  function* increaseSaga(param) {
  const { increase, decrease } = slice.sAction;
  yield delay(1000); // 1초를 기다립니다.
  if(param.type === INCREASE_ASYNC){
    yield put(increase(param.payload));
  }
  else{
    yield put(decrease(param.payload));
  }
}

보면 import한 slice의 해당 action을 가져오는데 increase, decrease를 가져옵니다.

const { increase, decrease } = slice.sAction;

그리고나서 slice.sAction에서 받은 increase를 put해주어 sliceReducer로 넘어가 해당 increase를 실행을 시킵니다.

이제 sliceReducer랑 sagaContainer를 연결해줍시다.

rootReducer

import { combineReducers } from 'redux';
import * as slice from './sliceReducer';
import * as saga from './sagaCounter';
import { all } from 'redux-saga/effects'
const rootReducer = combineReducers({[slice.sName] : slice.sReducer});
export function* rootSaga() {
    yield all([saga.counterSaga(),saga.counterSaga2()]); // all은 배열안의 여러 사가를 동시에 실행시킨다.
}
export default rootReducer;

combineReducers를 사용하여 reducer를 합쳐주고,
rootSaga 제너레이터 함수를 사용하여 saga들을 합쳐줍니다.

그리고 index.js에서 연결을 시켜줍니다.

index.js

import * as slice from './modules/sliceReducer';
import * as saga from './modules/sagaCounter'

import를 해주고

const sagaMiddleware = createSagaMiddleware(); // 사가 미들웨어를 만듭니다.
const store = configureStore({reducer : rootReducer, middleware : [ sagaMiddleware]}); // 리듀서랑 사가 연결
sagaMiddleware.run(rootSaga); // 루트 사가를 실행해줍니다.
ReactDOM.render(
  <React.StrictMode>
     <Provider store={store}>
        <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

이렇게 작성해주면 연결이 되었습니다.

profile
studying

0개의 댓글