20210701 Redux07 : vanilla redux vs redux-actions vs redux-toolkit
Redux 07
redux-actions 활용과 느낌
기존 환경
- redux-actions를 사용하는 경우, 기본적으로 다른 여러가지 패키지들과 사용하게 된다.
    
redux-actions: 리덕스 액션스는 덕스 패턴을 만들때 편하게 작성시켜 코드를 좀더 깔끔하게 해준다.react-redux: redux와 react를 연결할 때 쓰는 Provider, 그리고 react에서 state의 값을 가져오거나, 변경하고자 할때 요청을 useSeletor, useDispatch를 통해서 쉽게 사용할 수 있게함redux-thunk: 비동기 작업을 Action으로 수행하고, 해당 작업을 통해 가져온 데이터 같은 것을 redux에 반영하게 할 수 있음redux-devtools-extension: 리덕스 작업시 브라우저에서 어떻게 처리되는지 볼수 있게 추적해주고, 브라우저에서도 개발 tool이 아주 잘 되어 있음
 
redux-actions 사용 전
- redux-thunk, react-redux, redux-devtools-extension을 사용하는 것은 똑같음
 - 그러나, Type 작성, actionCreator 작성, defaultState작성, Reducer 작성 등의 한개의 모듈안에 엄청난 코드가 들어가게 된다.
    
- 단지 외부에서 데이터를 요청해서 가져오는 비동기 작업 1번 했을 뿐인데, start - success - fail 이렇게 3가지 경우의 수로 나누어 state 변경(dispatch) 처리를 해줘야 한다.
 
 - 그렇기 때문에, redux를 쓰면서 너무 긴 코드와 반복, 중복이 난무하는데 어떻게 해야 하나 생각했다.
    
- 기존에 만들어 놓은 instagram clone 프로젝트를 redux화 시킬려고 했는데 워낙 쓰이는 변수도 많은데, 이걸 구현하려면 이렇게 코드양이 많은 구조의 redux를 사용해야 한다고 생각니 엄두가 나지도 않았다.
 - 그래서 redux-actions에 발을 들여 어떤지 사용해 보고, 숙달 시키려고 노력했다.
 
 
import axios from "axios";
// Action Types
// thunk
const GET_USERS_START = "redux-start/users/GET_USERS_START";
const GET_USERS_SUCCESS = "redux-start/users/GET_USERS_SUCCESS";
const GET_USERS_FAIL = "redux-start/users/GET_USERS_FAIL";
// ActionsCreator
export function getUsersStart() {
  return {
    type: GET_USERS_START,
  };
}
export function getUsersSuccess(data) {
  return {
    type: GET_USERS_SUCCESS,
    data,
  };
}
export function getUsersFail(error) {
  return {
    type: GET_USERS_FAIL,
    error,
  };
}
function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// Reducer
export default function reducer(state = initialState, action) {
  // thunk
  if (action.type === GET_USERS_START) {
    return {
      ...state,
      loading: true,
      error: null,
    };
  }
  if (action.type === GET_USERS_SUCCESS) {
    return {
      ...state,
      loading: false,
      data: action.data,
    };
  }
  if (action.type === GET_USERS_FAIL) {
    return {
      ...state,
      loading: false,
      error: action.error,
    };
  }
  return state;
}
// Thunk ActionCreator : 비동기 작업
export function getUsersThunk() {
  return async (dispatch, getState, { history }) => {
    try {
      console.log(history);
      dispatch(getUsersStart());
      // API가 빨리 끝나서 분석을 위해서 sleep을 줌
      await sleep(1000);
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
      history.push("/");
    } catch (error) {
      dispatch(getUsersFail(error));
    }
  };
}
redux-actions 사용 후
- 활용해서 작성해 보았지만, prefix를 작성할 때 편한 것 말고는 딱히 크게 코드양을 줄어드는 것도 아닌것 같았다.
 - 어쨌거나, actionCreator를 작성해야 했다.
    
- 이때 actionsCreator와 reducer가 중복되는 곳이 많은데 이렇게 밖에 못줄이나 생각이 들어 아쉽긴 했다.
 
 - 대신, 기존에 사용하던 환경에서 action, reducer 작성 방식만 달라져서 reducerCombine 이나, middleware 설정은 건드리지 않아서 좋았다.
 
import axios from "axios";
import { createActions, handleActions } from "redux-actions";
// ActionsCreator (redux-actions)
const { getUsersStart, getUsersSuccess, getUsersFail } = createActions(
  {
    GET_USERS_SUCCESS: (data) => data,
    GET_USERS_FAIL: (error) => error,
  },
  "GET_USERS_START",
  {
    prefix: "redux-start/users",
  }
);
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// handleActions
// handleActions(reducerMap, defaultState[, options])
const reducer = handleActions(
  {
    GET_USERS_START: (state) => ({
      ...state,
      loading: true,
    }),
    GET_USERS_SUCCESS: (state, action) => ({
      ...state,
      loading: false,
      data: action.payload,
    }),
    GET_USERS_FAIL: (state, action) => ({
      ...state,
      loading: false,
      error: action.payload,
    }),
  },
  initialState,
  { prefix: "redux-start/users" }
);
export default reducer;
function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}
// Thunk ActionCreator : 비동기 작업
export function getUsersThunk() {
  return async (dispatch, getState) => {
    try {
      dispatch(getUsersStart());
      await sleep(1000);
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
      // history.push('/');
    } catch (error) {
      dispatch(getUsersFail(error));
    }
  };
}
redux-actions 사용법
createActions()
- 
    
createActions(actionMap, ...identityActions[, options])- creatActions는 actionMap, identityActions에서 활용되는 
대문자 스네이크 케이스(UPPER_SNAKE_CASE)를 알아서, Type으로 사용하고 이를camelCase형식으로 변경해서 creator 함수 명으로 사용됨 - 
        
createActions에 넣는 인자는 총 3가지임
- 근데, actionMap, identityActions 중에 하나만 단독으로 사용하게 되어도 매개변수 위치 상관 없이, 알아서 인식함
 
- actionMap 형식의 경우는 action들을 넣은 객체(
{}) 형태의 묶음으로 state 변환 요청으로 dispatch시 바꿀 데이터를 주는 경우 reducer에서 payload로 들어오는 것을 제어 할 수 있음getUsersSuccess()creator를 사용해서getUserSuccess(data)처럼 데이터를 주는 경우- 받는 인자를 어떻게 가공해서 payload가 어떤 형태의 자료를 보낼지 결정
 GET_USERS_SUCCESS: (data, index, number) => {data: data, index: index, number: number}의 형식인 경우 payload는{data: data, index: index, number: number}를 전달함으로 써,- reducer에서는 
{dataState: action.payload.data, indexState: action.payload.index, ...state}이런 형식으로 dispatch 받은 값을 storeState에 반영할 수 있음 
 
- identityActions의 경우에는 딱히 payload가 없거나, 가공해야 할 필요가 없는 경우 
""를 활용한 string 형식으로 넣으면 알아서 해당 액션 함수를 만들어 줌 
- 마지막에 붙는 options는 필수는 아니고, 
prefix와namespace를 설정할 수 있음- 객체 형태(
{})로 넣어서 값을 지정해주면 됨 prefix: ducks 패턴에 맞게 type을 변경해주기 위한 prefix를 설정 할 수 있음namespace: ducks패턴 type 명에서 쓰이는/(Seprator)를 변경 할 수 있음
 - 객체 형태(
 
 
 - creatActions는 actionMap, identityActions에서 활용되는 
 
// ActionsCreator (redux-actions)
export const { getUsersStart, getUsersSuccess, getUsersFail } = createActions(
  {
    GET_USERS_SUCCESS: (data) => data,
    GET_USERS_FAIL: (error) => error,
  },
  "GET_USERS_START",
  {
    prefix: "redux-start/users",
  }
);
handleActions
- handleActions는 해당 Action들을 처리하는 reducer의 역할을 함
 handleActions(actionMap[, defaultState], options)- handleActions의 경우에는 createActions와 다르게, identityActions의 형태로 다룰수는 없다.
 - actionMap
        
- 무조건 actionMap의 형태 
{}객체로 묶어 넣어 주어야 한다. - 똑같이 
UPPER_SNAKE_CASE형태로 작성하게 됨 - state, action을 인자로 받아서 return 처리하여 활용함
 - 주의할 점은, 각 reducer가 값을 처리할때 
...state처럼 기존의 state를 복사하고 해당 값에 변화하는 값을 덮어 씌우는 형태로 return 처리를 해줘야 한다는 것이다. - defaultState를 설정했다고 해서 reducer에서 값 변환시 자동으로 state의 변화하지 않는 값을 유지하게 해주진 않는다.(그래서 
...state를 사용해서 복사를 시켜줘야 함) 
 - 무조건 actionMap의 형태 
 - defaultState
        
- defaultState의 경우에는 단지, 처음에 앱이 실행되었을 때 reducer를 통해서 storeState의 상태를 성절해주는 역할 뿐인것 같다.
 
 - options
        
- 옵션은 createActions와 마찬가지의 기능하고, createActions에서 만들어지면서 옵션을 가진 action들을 reducer가 처리할 때 이러한 옵션을 해독하기 위한 
prefix,namespace를 설정해준다고 볼수 있겠다. 
 - 옵션은 createActions와 마찬가지의 기능하고, createActions에서 만들어지면서 옵션을 가진 action들을 reducer가 처리할 때 이러한 옵션을 해독하기 위한 
 
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// handleActions
// handleActions(reducerMap, defaultState[, options])
const reducer = handleActions(
  {
    GET_USERS_START: (state) => ({
      ...state,
      loading: true,
    }),
    GET_USERS_SUCCESS: (state, action) => ({
      ...state,
      loading: false,
      data: action.payload,
    }),
    GET_USERS_FAIL: (state, action) => ({
      ...state,
      loading: false,
      error: action.payload,
    }),
  },
  initialState,
  { prefix: "redux-start/users" }
);
export default reducer;
- 비동기 작업의 경우에는 thunk를 사용하던 방식으로 만들면 된다.
 
redux-toolkit 활용과 느낌
redux-actions를 활용하면서, 실망감을 느끼고 redux-tookit을 사용해 보았다. 일단, redux-toolkit에서 제공하는 createAction과 createReducer를 사용해 보았고, 이둘의 사용보다는 redux-tookit의 slice 사용이 가장 맘에 들었다.
- slice를 사용하면, reducer 작성 한번으로 action까지 다룰수 있게 되어 상당히 코드를 절약 할 수 있다. (중복제거를 통한 코드 축약)
 - thunk, redux-devtools-extension이 내장 되어있어서 따로 설치하지 않고도, 바로 사용이 가능하다. (환경의 용이성)
 - thunk를 활용해서 비동기 작업을 하고 싶은 경우에는 내장함수인 
createAsyncThunk()를 활용해서 구현할 수 있다 (비동기 처리의 용이성)- 기존의 thunk와 다른점은 promise middleware 처럼 비동기 작업을 수행하는 action을 만들면 알아서, 해당 action을 참고해서 
pending, fulfilled, rejected의 type을 가진 action을 만들어 주기 때문에 편함 - slice에서 
extraReducers에 넣어 활용하면 slice 기존 Reducers에 연결된 state의 값을 변화를 줄 수 있다.- 주의) 비동기 작업의 reducer를 slice Reducers에 구현하면, 작동하지 않음
 - 비동기 작업의 경우 애초에 slice 외부에서 만들어 지기 때문에 extraReducers를 통해 reducer를 구현해야 함
 
 
 - 기존의 thunk와 다른점은 promise middleware 처럼 비동기 작업을 수행하는 action을 만들면 알아서, 해당 action을 참고해서 
 
- slice에 붙은 Reducers의 경우에는 비동기 작업하는 함수는 딱히 필요 없고, state 값을 변경하는 경우에 사용하는데, actionCreator 함수명으로 작성하면 되고, reducer 작업처리는 
state.push()에 바뀔 데이터만 넣어주면 해당 데이터만 변경되고 나머지는 그대로라서...state처리를 할 필요가 없다.- 하지만, return 형식으로 사용하게 된다면 원래 쓰던대로 사용하고 
...state처리를 해줘야 한다. state.push()의 경우에는 return 처리가 아니라는 점을 주의해야 한다.
 - 하지만, return 형식으로 사용하게 된다면 원래 쓰던대로 사용하고 
 
import axios from "axios";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
// defaultState
const initialState = {
  loading: false,
  data: [],
  error: null,
};
// 비동기 작업
export const getUsersThunk = createAsyncThunk(
  "users/getUsersThunk",
  async (thunkAPI) => {
    try {
      await sleep(1000);
      const res = await axios.get("https://api.github.com/users");
      return res.data;
    } catch (error) {
      return thunkAPI.rejectWithValue(error);
    }
  }
);
// Slice
const users = createSlice({
  name: "redux-test/users",
  initialState,
  reducers: {
    /*
    // state.push 함수로 처리하는 경우 -> getUsersStart
     getUsersStart: (state, action) => {
      state.push({ loading: true });
    },
    // return으로 처리하는 경우 -> getUsersSuccess
    getUsersSuccess: (state, action) => ({
      loading: false,
      data: action.payload,
    }),
    getUsersFail: (state, action) => {
      state.push({ loading: false, error: action.payload });
    }, */
  },
  extraReducers: {
    [getUsersThunk.pending]: (state, action) => ({
      ...state,
      loading: true,
    }),
    [getUsersThunk.fulfilled]: (state, action) => ({
      ...state,
      loading: false,
      data: action.payload,
    }),
    [getUsersThunk.rejected]: (state, action) => ({
      ...state,
      loading: false,
      error: action.payload,
    }),
  },
});
export default users;
function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}
- 다음시간에는 구체적인 사용방법과 특징을 작성할 예정