최근 집에서 쉬는 시간에 한국의 해커뉴스를 타깃으로 하는 리드포스트라는 웹사이트를 개발하고 있습니다.
(물론 만들다보니 욕심때문에 해커뉴스와는 전혀 다른 사이트가 되어가고 있습니다. 😆)
회사에서는 적용하지 못한 실험적인 것들을 이 사이드 프로젝트를 진행하면서 많이 적용해보고 있습니다. 그 중에서 오늘은 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를 한번 사용해보시면 좋을 것 같습니다.

감사합니다.