타입 손실 없이 actionCreator를 dispatch로 래핑하기

다음 코드를 보자. redux 예제로 많이들 사용하는 counter example이다.

counterActions라는 action creator를 사용해서 action을 만든다. 이 counterActions 오브젝트는 각 키에 맞는 액션 생성함수를 갖는다. (따라서, action을 생성하기 위해 counterActions.increment()를 호출한다.) 이렇게 만들어진 액션을 바로 dispatch()에 넘기는 단순한 코드다. 이 단순하지만 반복되는 dispatch의 래핑 중복을 없애고 싶었다.

간단하게 구현해보자.

대략 이런 모양이 될 것이다. 그리고 다음과 같이 사용하면 된다.

와! 더 이상 dispatch로 매번 감싸지 않아도 실행된다!

고… 끝나면 너무 시시하겠지? ㅎㅎ.

발상 자체는 흥미롭지만, 구현에 큰 단점이 있다. 눈치 챘겠지만 naiveWrapWithDispatch()는 js다. 즉, dispatch로 감싸주면서 타입의 정보를 잃어버렸다. 파라미터를 받지 않는 단순한 함수 (increment(), decrement())는 상관없을지 몰라도, 파라미터를 받는 action의 타입 정보를 잃는 것은 매우 뼈아프다. 이러려고 TypeScript를 썼나, 자괴감 들고 괴롭다.

그럼 타입을 살려보자.

redux의 dispatch() 함수를 보면 인자로 들어온 action을 그대로 반환하는 것을 확인할 수 있다. 이에 유의하며 typing을 진행하면 다음과 같다.

내가 사랑하는 TypeScript action creator인 deox에서 ActionCreator 타입을 가져온다. 코드에서, 우리는 액션 하나의 타입을 정의하는 것이 아니라, { 키 -> 액션 }의 actions 객체를 사용하기에 그 타입을 ActionsType으로 명명했다. (s가 있다!)

중간에 js식으로 적당히 때려넣는 것은 똑같고,

마지막 return 부분에서 as를 사용하여 캐스팅을 하는데, 원래 actionCreator가 입력된 action을 반환하는 것과 같이, 우리의 dispatch로 래핑된 actionCreator 또한 입력된 action을 반환한다. 즉, 중간에 dispatch만 추가된 것이기 때문에 우리는 res의 타입을 T로 캐스팅할 수 있다.


너무 쉽게 끝나서 아쉬우니, Dispatch가 undefined를 반환하는, void 함수라고 가정하고 마지막 return 부분을 다시 고쳐보면 다음과 같다.

[key in keyof T] 부분은 (약간 Python 문법을 빌려 표현해보자면) 다음과 같이 해석된다. [key in ('increment', 'decrement', 'reset')]. 즉, 타입 T를 갖는 actions객체에 속한 키들을 뜻한다. 그 각 키들을 key로 하는 Object이고, 이 Object의 value는 T[key]의 파라미터를 갖고, undefined를 리턴하는 함수라는 뜻이다. 이렇게 작성하면 우리는 action의 파라미터를 받아서 action을 생성했던 actionCreator의 dict 객체를, action의 파라미터를 받는 void 함수로 타입을 바꿀 수 있다!

사실, 처음에는 Dispatch를 void 함수로 착각해서 이런 코드를 짰고, 그게 억울해서 포스팅해봤다. 에휴.

Leave a Reply