Thief of Wealth

2022년 4월 17일에 react-redux가 릴리즈 되었네요.

 

react-redux v7 버전대를 사용하다가 최근에 큰 체감을 느낀 부분이 있어서 포스팅을 하게 되었습니다.

 

원문은 이 링크를 참고하세요 :)

 

또한 DefaultRootState가 사라지게된 이유는 여기를 참고해주세요.

 

 

최근까지만해도 redux를 사용할때 

declare module 'react-redux' {
  interface DefaultRootState extends RootState {}
}

이렇게 선언하여서 

const someState = useSelector((state: RootState) => state.someReducer);

으로 타입스크립트에서 쓰던것을

const someState = useSelector(state => state.someReducer);

으로 편하게 사용할 수 있었습니다.

 

꽤 오랫동안 redux 사용자들 사이에서 꿀팁처럼 여겨졌던 방법이죠.

 

v7 까지만해도 useSelector의 state인자의 타입이 DefaultRootState 라서 가능했던 방법입니다.

 

하지만 이런이유로 인해서 v8에서 사라지게 되었습니다.

제 짧은 독해실력으로 요약하자면,

라이브러리 함수의 타입을 강제로 덮어쓰는 위와같은 방법(위 코드)이 전 세계적으로 널리퍼졌고 이것이 결코 좋은 방향이라고 생각하지 않는다는 것입니다.

 

이런 패턴이 널리사용됨으로써 useSelector를 덮어쓰는 코드들이 늘어나게 되었고 프로젝트의 interface를 오염시킨다고 보았기 때문인것 같습니다. (프로젝트와 redux라이브러리간의 타입간섭)

 

그래서 state의 기본 타입을 unkown으로 바꾼것입니다.

 

이제 v8에서 useSelector는 다음과 같이 정의됩니다.

export declare const useSelector: <TState = unknown, Selected = unknown>(selector: (state: TState) => Selected, equalityFn?: EqualityFn<Selected> | undefined) => Selected;

 

이제 다시 

const someState = useSelector((state: RootState) => state.someReducer);

이렇게 써야하냐구요? 

사실 그것을 의도하는 것 같기는 하지만, 해당 이슈에 누군가 다른 방법을 찾아놓았습니다.

 

// store.d.ts

import { RootState } from './store';

declare module 'react-redux' {
  export declare const useSelector: <T>(
    selector: (state: RootState) => T,
    equalityFn?: EqualityFn<T>,
  ) => T;
}

위와 같이 타입을 declare해놓으면 v8에서도 RootState 정의없이 사용하실 수 있습니다. :)

 

하지만 이것이 정말 좋은 코드인지는 모르겠습니다.

솔직히 개발자 입장에서는 useSelector를 쓸때마다 RootState를 import하고 타입을 선언하는것은 중복된 행동이고 매력적이진 않죠.

반대로 redux 메인테이너 입장에서는 제 3라이브러리인 redux의 interface를 많은 프로젝트에서 강제로 덮어쓰니까 타입이 오염된다고 느끼고 있는것 같습니다.

 

개발하기 편한코드가 좋은 코드인가? 아니면 코드 창작자가 의도한 코드가 좋은 코드인가?에 대해서 고민해볼 수 있는 기회가 되었던 업데이트였습니다. 여러분들도 한번 고민해보아요 :)

 


질문) v8에서 useSelector의 state를 RootState로 해버리면 되잖아? 왜 그렇게 안했데!

redux-toolkit를 안쓰고 react-redux 만 쓰는 사람들은 사실 redux를 적용하는 방법이 제각각입니다.

가장 널리사용되는것이 store.ts 에 다음과 같이 정의하여 사용하는 것이지요.

...
const store = createStore(rootReducer, compose(composeWithDevTools(applyMiddleware(thunk))));

export default store;
export type RootState = ReturnType<typeof rootReducer>;
export type RootAction = ReturnType<typeof store.dispatch>;
export type AppDispatch = ThunkDispatch<RootState, any, RootAction>;

그래서 저는 react-redux를 구현하는 방법이 다를수도 있으니까

useSelector의 state를 RootState로 단언할 수 없었다고 생각합니다. (뇌피셜입니다)

 

profile on loading

Loading...