리덕스를 사용하는 프로젝트에서 액션 생성 함수 & 리듀서를 훨씬 쉽고 깔끔하게 작성 가능하게 하는 라이브러리
설치 방법
$ yarn add typesafe-actions
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;
createReducer
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 를 활용하면 되긴 함.
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를 설정
action
액션 객체를 만드는 함수
이 것만 사용 시, Action Helpers
(https://www.npmjs.com/package/typesafe-actions#action-helpers-api) 지원 불가
예시 코드
export const addTodo = (text: string) => action(ADD_TODO, { id: nextId++, text })
// src/App.tsx
import React from 'react';
import TodoApp from './containers/TodoApp';
const App: React.FC = () => {
return <TodoApp />;
};
export default App;
참고