TypeScript 환경에서 Redux Boilerplate 없애기

Heejin Lee·2018년 12월 2일
6
post-thumbnail

최근 집에서 쉬는 시간에 한국의 해커뉴스를 타깃으로 하는 리드포스트라는 웹사이트를 개발하고 있습니다.
(물론 만들다보니 욕심때문에 해커뉴스와는 전혀 다른 사이트가 되어가고 있습니다. 😆)
회사에서는 적용하지 못한 실험적인 것들을 이 사이드 프로젝트를 진행하면서 많이 적용해보고 있습니다. 그 중에서 오늘은 TypeScript 환경에서 ReduxBoilerplate를 줄이는 방법에 대해서 소개해보려고 합니다.

Redux는 Boilerplate가 많이 필요한 것으로 유명합니다. 특히 Action과 관련된 부분에 Boilerplate가 많이 필요합니다. 거기다가 TypeScript를 사용해서 개발하다보면 일반 JavaScript보다 더 많은 Boilerplate 코드가 생깁니다. (하지만 더 안전해지고, 리팩토링이 더 쉬워집니다.)

다음은 TypeScript 환경에서 제가 선호하는 기본 스타일의 Redux 코드입니다.

// actions.ts
enum UserActionTypes {
  SetUserName = 'SET_USER_NAME'
}

namespace UserActions {
  export function setUserName(name: string) {
    return { type: UserActionTypes.SetUserName, name };
  }
}
// reducer.ts
function setUserName(state: UserState, action: Action<typeof UserActions.setUserName>) {
  return { ...state, name: action.name };
}

const reducer = createReducer({
  [UserActionTypes.SetUserName]: setUserName
});

약간 복잡해보이지만 아직까지는 견딜 수 있을 정도의 복잡함입니다. 그러나 시간이 지날수록 반복되는 코드가 많아지고, 그에 따라서 개발자의 실수도 더 많아지죠.
어떻게 하면 이 문제를 해결할 수 있을까 고민을 했었는데 명확한 답을 찾지 못하고 있었습니다.

그러다가 얼마 전에 immer-reducer라는 라이브러리를 알게 되었습니다. 네 그렇습니다. 답은 immer-reducer입니다. immer-reducer를 이용하면 Boilerplate를 90% 이상 없앨 수 있습니다.
아래는 위 Redux 코드를 immer-reducer를 사용한 형태로 바꾼 코드입니다.

class UserImmerReducer extends ImmerReducer {
  public setUserName(name: string) {
    this.draftState.name = name;
  }
}

const userReducer = createReducerFunction(UserImmerReducer);
const userActions = createActionCreators(UserImmerReducer);

immer-reducer의 매직으로 마치 일반적인 클래스를 구현하는 것처럼 Redux Reducer/ActionCreators를 구성할 수 있습니다.
약간의 매직으로 인해서 어떻게 동작하는건지 바로 이해가 되지는 않지만, 코드는 심플한 형태를 가지게 됩니다. Boilerplate도 거의 없죠. (매직이 조금 추가되었지만 Redux가 익숙한 사람들에게는 어느 정도의 매직은 괜찮지 않나 생각합니다.)

기본 아이디어는 Object.getOwnPropertyNames를 이용해서 클래스의 멤버 함수 이름을 알아내고, 함수 이름과 인자 타입을 기반으로 ActionCreators를 만들고, immer의 draftState를 주입해주는 Reducer 함수를 만들어내는 것이죠. 단순하지만 강력한 아이디어라고 생각합니다.
Redux Boilerplate로 고통받고 계신다면 immer-reducer를 한번 사용해보시면 좋을 것 같습니다.

감사합니다.

7개의 댓글

comment-user-thumbnail
2018년 12월 2일

TypeScript 를 쓰게 될 때 개발자들마다 선호하는 방식이 정말 다르군요 ^^
저도 한번 제가 TypeScript + Redux 를 사용하게 될 때 어떻게 쓰는지 조만간 공유해보겠습니다!

벨로그 방문해주셔서 감사합니다!!

답글 달기
comment-user-thumbnail
2018년 12월 3일

멋지네요!

답글 달기
comment-user-thumbnail
2018년 12월 11일

오야.... immer-reducer 한번 보고 왔는데... 좋네요.. reducer 랑 action 을 저렇게 줄여주다니... 흠.. 당장 다음 작업에 적용해서 한번 써봐야겠네요. 그리고 readpost 도 가봤는데 내용 좋은것들만 액기스로 있어서 계속 보게되네요 ^^ 그냥 잠깐 든 생각인데 readpost 의 내용을 월간이나 주간으로 요약되서 velog 에 주기적으로 올라오면 엄청 좋을것 같다는 생각이 잠깐 들었어요 ^^

1개의 답글
comment-user-thumbnail
2018년 12월 12일

제가 지금 테스트로 immer-reducer 를 사용해보고 있는데요 혹시 async action 은 어떻게 구현하셨나요? sync action 들은 기존꺼와 잘 작동하는데 async action 에서 this.draftState 를 할려고 하면 'Cannot perform 'set' on a proxy that has been revoked' 이러면서 에러가 나고 있어서...

1개의 답글