• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    미해결

useReducer 사용시 dispatch 함수에 generic으로 action 타입을 넘겨줄 때 에러가 발생합니다.

21.04.29 07:58 작성 조회수 503

0

제로초님 안녕하세요. 

TicTacToe 코드에서 질문이 있습니다. 

const TicTacToe = () => {
  const [state, dispatch] = useReducer<Reducer<ReducerState, ReducerActions>>(reducer, initialState);
  ... 
  if (win) {
    dispatch<SetWinnerAction>({ type: SET_WINNER, winner: turn });
    dispatch({ type: RESET_GAME});
  } ...
}

 위의 코드에서 SET_WINNER action을 dispatch 하는 부분에서처럼 dispatch에 generic으로 SetWinnerAction 인터페이스를 줘서 표기를 확실히하고 싶은데, 저렇게 하면 SetWinnerAction 부분에서 타입에러가 나면서 "Expected 0 arguments, but got 1" 이라는 메시지가 뜹니다. 현재 웹스톰 사용 중인데, type definition을 따라가보면 useReducer가 

function useReducer<R extends ReducerWithoutAction<any>, I>(
reducer: R,
initializerArg: I,
initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];

여기에 매칭이 되더라고요. 

vscode로 보면 

 function useReducer<R extends Reducer<anyany>>(
        reducer: R,
        initialState: ReducerState<R>,
        initializer?: undefined
    ): [ReducerState<R>, Dispatch<ReducerAction<R>>];

여기에 매칭이 되기는 하는데 어쨌든 IDE에나 컴파일해봤을 때나 같은 에러가 뜹니다. 

이건 어떤 식으로 해결할 수 있을까요? 

감사합니다. 

답변 2

·

답변을 작성해보세요.

0

mhr님의 프로필

mhr

질문자

2021.04.29

제로초님 안녕하세요. 빠른 답변 감사합니다. 

function useReducer<R extends ReducerWithoutAction<any>, I>(
reducer: R,
initializerArg: I,
initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
/**
* An alternative to `useState`.
*
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
* multiple sub-values. It also lets you optimize performance for components that trigger deep
* updates because you can pass `dispatch` down instead of callbacks.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usereducer
*/
// overload where dispatch could accept 0 arguments.
function useReducer<R extends ReducerWithoutAction<any>>(
reducer: R,
initializerArg: ReducerStateWithoutAction<R>,
initializer?: undefined
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
/**
* An alternative to `useState`.
*
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
* multiple sub-values. It also lets you optimize performance for components that trigger deep
* updates because you can pass `dispatch` down instead of callbacks.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usereducer
*/
// overload where "I" may be a subset of ReducerState<R>; used to provide autocompletion.
// If "I" matches ReducerState<R> exactly then the last overload will allow initializer to be omitted.
// the last overload effectively behaves as if the identity function (x => x) is the initializer.
function useReducer<R extends Reducer<any, any>, I>(
reducer: R,
initializerArg: I & ReducerState<R>,
initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
/**
* An alternative to `useState`.
*
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
* multiple sub-values. It also lets you optimize performance for components that trigger deep
* updates because you can pass `dispatch` down instead of callbacks.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usereducer
*/
// overload for free "I"; all goes as long as initializer converts it into "ReducerState<R>".
function useReducer<R extends Reducer<any, any>, I>(
reducer: R,
initializerArg: I,
initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
/**
* An alternative to `useState`.
*
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
* multiple sub-values. It also lets you optimize performance for components that trigger deep
* updates because you can pass `dispatch` down instead of callbacks.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usereducer
*/

// I'm not sure if I keep this 2-ary or if I make it (2,3)-ary; it's currently (2,3)-ary.
// The Flow types do have an overload for 3-ary invocation with undefined initializer.

// NOTE: without the ReducerState indirection, TypeScript would reduce S to be the most common
// supertype between the reducer's return type and the initialState (or the initializer's return type),
// which would prevent autocompletion from ever working.

// TODO: double-check if this weird overload logic is necessary. It is possible it's either a bug
// in older versions, or a regression in newer versions of the typescript completion service.
function useReducer<R extends Reducer<any, any>>(
reducer: R,
initialState: ReducerState<R>,
initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

위의 파일이 제가 현재 사용하고 있는 버전의 useReducer가 정의되어 있는 react의 index.d.ts 파일인데요. 

리턴값들을 보시면 첫 번째와 두 번째 정의의 리턴값은 DispatchWithoutAction을 리턴하지만, 나머지 3, 4, 5번째의 리턴값의 dispatch 함수는 Dispatch<ReducerAction<R>> 입니다. 이 경우는 action을 받을 수 있는 거 아닌가 생각했는데요. 

리액트 노드버드 강좌 들을 때 제 나름대로 타이핑할 때 dispatch 함수에 제네릭을 줬던 기억이 나서 확인해보니 useDispatch는 react-redux의 index.d.ts 파일에 

export function useDispatch<TDispatch = Dispatch<any>>(): TDispatch;
export function useDispatch<A extends Action = AnyAction>(): Dispatch<A>;

이렇게 정의되어 있더라고요. useDispatch의 Dispatch<any>나 위의 useReducer의 Dispatch<ReducerAction<R>>이나 마찬가지인 것 같아서 그렇게 사용할 수 있지 않을까 생각했습니다. 그래서 위의 useReducer 타입 정의 중에서 마지막 것에 맞춰보려고 useReducer의 generic을 이래저래 다르게 줘봐도 잘 안 되더라고요. 오버로딩 가능한 것들 중에서 엉뚱한 것을 가져와서 에러가 나는 경우에는 타입을 맞춰보라고 강의 중에 말씀하셨던 것 같은데, 이 경우는 어떻게 해결할 수 있을지 궁금하네요. 감사합니다. 

저 코드에 따르면 dispatch는 Dispatch<ReducerAction<R>>인데 이미 <R>이 정해져있습니다. 저희가 useReducer할 때 제네릭으로 넣었습니다(Reducer<ReducerState, ReducerActions>). ReducerAction<R>은 그 R(Reducer)에서 ReducerActions를 꺼내는 것이고요. 따라서 dispatch에는 이미 ReducerActions가 적용되어 있습니다. 그래서 dispatch는 아무런 제네릭을 받지 못합니다.

mhr님의 프로필

mhr

질문자

2021.04.29

그런 것이었군요. 이해했습니다. 감사합니다!

0

제네릭은 마음대로 쓸 수 없습니다. 이미 useReducer을 호출할 때 dispatch의 매개변수가 ReducerActions로 결정되었습니다. 

dispatch({ type: SET_WINNER, winner: turn });

dispatch는 제네릭을 받지 않습니다.