TypeScript 최신 기능을 활용한 Redux 액션 타이핑

Kim Seungha
5 min readMay 26, 2019

--

이 글에서는 최신 TypeScript 기능을 활용해서 Redux 액션 타이핑을 좀 더 쉽게 할 수 있는 방법에 대해 다룹니다.

Redux 코딩을 하다 보면 많은 수의 action type과 action creator를 만들게 됩니다. 이 때 액션 객체의 모양이 조금씩 달라서, 주의하지 않으면 원래 의도한 사용법과 다르게 액션 객체를 사용하는 실수를 범할 수 있습니다.

// JavaScriptconst ADD_TODO = 'ADD_TODO'function addTodo(title) {
return {
type: ADD_TODO,
payload: {
title
}
}
}
// ...리듀서에서
function reducer(state, action) {
switch(action.type) {
case ADD_TODO:
return {
// *** 버그! action.payload.title 이라고 써야 합니다.
todos: [...state.todos, action.title]
}
}
}

이런 실수를 방지하기 위해, TypeScript의 여러 기능을 활용할 수 있습니다. Redux 공식문서에는 아래와 같이 interface를 사용하는 예제가 나와있습니다. (편의상 state 타이핑은 생략했습니다.)

// TypeScript - interface 사용const ADD_TODO = 'ADD_TODO'interface AddTodoAction {
type: typeof ADD_TODO,
payload: {
title: string
}
}
function addTodo(title: string): AddTodoAction {
return {
type: ADD_TODO,
payload: {
title
}
}
}
// ...리듀서에서
function reducer(state: any, action: AddTodoAction) {
switch(action.type) {
case ADD_TODO:
return {
// *** 컴파일 타임에 에러가 잡힙니다. 자동 완성 역시 잘 됩니다.
todos: [...state.todos, action.title]
}
}
}

이렇게 컴파일 타임에 타입 에러를 잡아낼 수 있게 되었습니다.

하지만 이 코드에는 약간의 문제가 있습니다. JavaScript와 비교했을 때 코드의 길이가 더 길어졌습니다. 이는 action creator에서 반환하는 객체, 즉 액션 객체의 모양을 나타내기 위해 interface를 추가로 작성해주어야 하기 때문입니다. 사실 액션 객체의 모양은 action creator에서 반환해주는 객체에 이미 잘 표현되어 있는데, 같은 모양을 중복 작성하는 느낌이 듭니다. 만약 action creator의 개수가 많다면, 그 개수만큼 interface를 중복 작성해야 할 것입니다.

TypeScript 2.8 버전에 추가된 ReturnType 제네릭 타입, TypeScript 3.4 버전에 추가된 const assertion 기능을 활용하면, 위 코드를 중복 없이 JavaScript와 비슷한 길이로 작성할 수 있게 됩니다.

// TypeScript - ReturnType 사용const ADD_TODO = 'ADD_TODO'function addTodo(title: string) {
// *** const assertion - type 속성을 타입추론 시 활용할 수 있게 하기 위함
return <const>{
type: ADD_TODO,
payload: {
title
}
}
}
// *** interface 대신 ReturnType을 활용해 중복 제거
type AddTodoAction = ReturnType<typeof addTodo>
// ...리듀서에서
function reducer(state: any, action: AddTodoAction) {
switch(action.type) {
case ADD_TODO:
return {
todos: [...state.todos, action.payload.title]
}
}
}

ReturnType은 특정 함수의 반환 타입을 추출해내는 제네릭 타입입니다. 이를 통해 interface 중복 작성을 피할 수 있게 되었습니다.

const assertion에 대해서는 제가 이전에 썼던 글을 참고하시기 바랍니다.

여러 action creator에 대한 타이핑을 하려면, 아래와 같이 union type 기능을 활용하면 됩니다.

function addTodo(title: string) { ... }
function updateTodo(title: string) { ... }
function removeTodo(id: string) { ... }
type Action = ReturnType<typeof addTodo>
| ReturnType<typeof updateTodo>
| ReturnType<typeof removeTodo>
// ...리듀서에서
function reducer(state: any, action: Action) { ... }

--

--