Redux+TS 02 | 리덕스 모듈 리팩토링 (typesafe-actions, 메서드체이닝)

Kate Jung·2022년 1월 2일
0

middlewares & libraries

목록 보기
12/17
post-thumbnail
post-custom-banner

📌 typesafe-actions 로 리팩토링

🔹 라이브러리 설명 및 설치법

  • typesafe-actions 란?

    리덕스를 사용하는 프로젝트에서 액션 생성 함수 & 리듀서를 훨씬 쉽고 깔끔하게 작성 가능하게 하는 라이브러리

  • 설치 방법

    $ yarn add typesafe-actions

🔹 counter 리덕스 모듈 리팩토링

◾ src/modules/counter.ts

import { deprecated, ActionType, createReducer } from "typesafe-actions";
const { createStandardAction } = deprecated;

// 액션 타입 선언
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
const INCREASE_BY = "counter/INCREASE_BY";

// 액션 생성함수 선언
export const increase = createStandardAction(INCREASE)();
export const decrease = createStandardAction(DECREASE)();
export const increaseBy = createStandardAction(INCREASE_BY)<number>();

// 모든 액션 객체들에 대한 타입 준비
const actions = { increase, decrease, increaseBy };
type CountAction = ActionType<typeof actions>;

// state 타입 선언 (이 리덕스 모듈에서 관리 할)
type CounterState = {
  count: number;
};

// 초기상태 선언
const initialState: CounterState = {
  count: 0,
};

// 리듀서
const counter = createReducer<CounterState, CountAction>(initialState, {
  [INCREASE]: (state) => ({ count: state.count + 1 }),
  [DECREASE]: (state) => ({ count: state.count - 1 }),
  [INCREASE_BY]: (state, action) => ({ count: state.count + action.payload }),
});

export default counter;

◾ 참고

typesafe-actions 최신 버전 설치 방법

import { deprecated, ActionType, createReducer } from 'typesafe-actions';
const { createAction, createStandardAction } = deprecated;

[ 액션 생성 함수 ] payload 타입을 Generics 로 설정

  • createStandardAction(액션 type 변수명)<payload 타입>()

    ex. export const increaseBy = createStandardAction(INCREASE_BY)<number>();

[ 액션 객체 타입 ] actions 객체 & ActionType 을 통한 모든 액션 객체 타입 준비

  • actions

    모든 액션 생성함수들을 actions 객체에 넣는다.

  • ActionType<typeof actions>

    ActionType 를 사용하여 모든 액션 객체들의 타입을 준비

  • 예시 코드

    const actions = { increase, decrease, increaseBy };
    type CounterAction = ActionType<typeof actions>;

[ 리듀서 ] createReducer

  • createReducer

    리듀서를 쉽게 만들 수 있게 해주는 함수

    • 리듀서를 객체 형태로 작성 가능 (switch 문 대신)

      switch 문 보다 코드 훨씬 간결

  • Generics로 상태(리듀서에서 관리할) & 모든 액션 객체들의 타입(리듀서에서 처리 할)을 넣어야 함.

  • 예시 코드

    const counter = createReducer<CounterState, CounterAction>(initialState, {
      [INCREASE]: state => ({ count: state.count + 1 }), // 액션을 참조 할 필요 없으면 파라미터로 state 만 받아와도 됨.
      [DECREASE]: state => ({ count: state.count - 1 }),
      [INCREASE_BY]: (state, action) => ({ count: state.count + action.payload }) // 액션 타입 -> 유추 가능
    });

🔹 렌더링

// src/App.tsx

import React from 'react';
import CounterContainer from './containers/CounterContainer';

const App: React.FC = () => {
  return <CounterContainer />;
};

export default App;

📌 counter 리덕스 모듈 다른 방식으로 리팩토링

🔹 설명

🔹 메서드 체이닝 방식

◾ src/modules/counter.ts

import { deprecated, ActionType, createReducer } from "typesafe-actions";
const { createAction, createStandardAction } = deprecated;

// 액션 type 선언
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
const INCREASE_BY = 'counter/INCREASE_BY';

// 액션 생성함수를 선언합니다
export const increase = createStandardAction(INCREASE)();
export const decrease = createStandardAction(DECREASE)();
export const increaseBy = createStandardAction(INCREASE_BY)<number>(); // payload 타입을 Generics 로 설정해주세요.

// 액션 객체 타입 준비
const actions = { increase, decrease, increaseBy }; // 모든 액션 생성함수들을 actions 객체에 넣습니다
type CounterAction = ActionType<typeof actions>; // ActionType 를 사용하여 모든 액션 객체들의 타입을 준비해줄 수 있습니다

// 이 리덕스 모듈에서 관리 할 상태의 타입을 선언합니다
type CounterState = {
  count: number;
};

// 초기상태를 선언합니다.
const initialState: CounterState = {
  count: 0
};

// 리듀서를 만듭니다
// createReducer 는 리듀서를 쉽게 만들 수 있게 해주는 함수입니다.
// Generics로 리듀서에서 관리할 상태, 그리고 리듀서에서 처리 할 모든 액션 객체들의 타입을 넣어야합니다
const counter = createReducer<CounterState, CounterAction>(initialState)
  .handleAction(INCREASE, state => ({ count: state.count + 1 }))
  .handleAction(DECREASE, state => ({ count: state.count - 1 }))
  .handleAction(INCREASE_BY, (state, action) => ({
    count: state.count + action.payload
  }));

export default counter;

◾ 장점

handleAction 의 첫번째 인자 → 액션 생성함수를 넣어도 작동

  • 생성 함수를 참조하여(액션의 type 대신) 리듀서를 구현

    → 액션의 type 을 선언 할 필요 x

  • 예시 코드 (src/modules/counter.ts)

    import { deprecated, ActionType, createReducer } from "typesafe-actions";
    const { createStandardAction } = deprecated;
    
    // 액션 생성함수 선언
    export const increase = createStandardAction("counter/INCREASE")();
    export const decrease = createStandardAction("counter/DECREASE")();
    export const increaseBy = createStandardAction("counter/INCREASE_BY")<number>();
    
    // 모든 액션 객체들에 대한 타입 준비
    const actions = { increase, decrease, increaseBy };
    type CountAction = ActionType<typeof actions>;
    
    // state 타입 선언 (이 리덕스 모듈에서 관리 할)
    type CounterState = {
      count: number;
    };
    
    // 초기상태 선언
    const initialState: CounterState = {
      count: 0,
    };
    
    // 리듀서
    const counter = createReducer<CounterState, CountAction>(initialState)
      .handleAction(increase, (state) => ({ count: state.count + 1 }))
      .handleAction(decrease, (state) => ({ count: state.count - 1 }))
      .handleAction(increaseBy, (state, action) => ({
        count: state.count + action.payload,
      }));
    
    export default counter;

◾ 생략 가능한 것

  • CounterAction (모든 액션 객체들의 타입) 준비

  • createReducer 사용 시, 해당 함수의 Generics 생략 가능

    상태의 타입액션 객체의 타입
    ~ 를 참조하여 유추 가능initialState액션 생성함수
  • 예시 코드 (src/modules/counter.ts)

    import { deprecated, createReducer } from "typesafe-actions";
    const { createStandardAction } = deprecated;
    
    // 액션 생성함수를 선언합니다
    export const increase = createStandardAction('counter/INCREASE')();
    export const decrease = createStandardAction('counter/DECREASE')();
    export const increaseBy = createStandardAction('counter/INCREASE_BY')<number>(); // payload 타입을 Generics 로 설정해주세요.
    
    // 이 리덕스 모듈에서 관리 할 상태의 타입을 선언합니다
    type CounterState = {
      count: number;
    };
    
    // 초기상태를 선언합니다.
    const initialState: CounterState = {
      count: 0
    };
    
    // 리듀서를 만듭니다
    // 상태의 타입은 initialState 를 참조하여 바로 유추 할 수 있고,
    // 액션 객체의 타입은 액션 생성함수를 참조하여 유추 할 수 있기 때문에 Generics를 생략해도 무방합니다.
    const counter = createReducer(initialState)
      .handleAction(increase, state => ({ count: state.count + 1 }))
      .handleAction(decrease, state => ({ count: state.count - 1 }))
      .handleAction(increaseBy, (state, action) => ({
        count: state.count + action.payload
      }));
    
    export default counter;

◾ 미들웨어 사용 시, 부적합 할 수도 있다.

미들웨어(redux-saga, redux-observable 등) 사용 시, 모든 액션 객체들/액션들의 type 을 사용해야 하는 일이 발생 가능
→ 위와 같은 구조가 부적합할 수 있음.

  • 해결 방법

    getType 를 활용하면 되긴 함.

📌 todos 리덕스 모듈 리팩토링

🔹 typesafe-actions  활용(src/modules/todos.ts)

import { deprecated, createReducer, ActionType } from "typesafe-actions";
const { createAction, createStandardAction } = deprecated;

// 액션 타입
const ADD_TODO = "todos/ADD_TODO";
const TOGGLE_TODO = "todos/TOGGLE_TODO";
const REMOVE_TODO = "todos/REMOVE_TODO";

// 새 투두 추가할 때 사용할 고유 ID
let nextId = 0;

// 액션 생성 함수
export const addTodo = createAction(
  ADD_TODO,
  (action) => (text: string) => action({ id: nextId++, text })
);
export const toggleTodo = createStandardAction(TOGGLE_TODO)<number>(); // payload가 그대로 들어가는 액션생성함수: 간단
export const removeTodo = createStandardAction(REMOVE_TODO)<number>();

// 모든 액션 객체들에 대한 타입 준비
const actions = { addTodo, toggleTodo, removeTodo };
type TodosAction = ActionType<typeof actions>;

// 투두 타입
export type Todo = {
  id: number;
  text: string;
  done: boolean;
};

// state 타입
export type TodosState = Todo[];

// state 초기값
const initialState: TodosState = [];

// 리듀서
const todos = createReducer<TodosState, TodosAction>(initialState, {
  [ADD_TODO]: (state, action) =>
    state.concat({
      ...action.payload, // id, text 를 이 안에 넣기
      done: false,
    }),
  // 비구조화 할당을 활용 -> payload 값의 이름을 바꿈.
  [TOGGLE_TODO]: (state, { payload: id }) =>
    state.map((todo) =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ),
  [REMOVE_TODO]: (state, { payload: id }) =>
    state.filter((todo) => todo.id !== id),
});
export default todos;

◾ 참고

[ 액션 생성 함수 ] createAction 를 사용하는 경우

  • 파라미터를 기반하여 커스터마이징된 payload를 설정

◾ 렌더링

// src/App.tsx
import React from 'react';
import TodoApp from './containers/TodoApp';

const App: React.FC = () => {
  return <TodoApp />;
};

export default App;

참고

profile
복습 목적 블로그 입니다.
post-custom-banner

0개의 댓글