TIL 231215 - UnknownAction 너 뭐야

송용승·2023년 12월 17일
1

TIL

목록 보기
24/29

Today I Learned

이번주에는 TypeScript를 학습했다. 처음 들어갈때만 해도 자신만만했던 나는
지금은
없다.. 그런 나는 없어..

사실 나는 타입을 꽤 잘 알고 있다고 생각했다. 어느 정도냐면 딱 내가 JS로 React 앱을 만들때

아 이거 타입만 지정해줄 수 있다면 오류가 훨~씬 덜 날 것 같은데

라고 생각할 정도였다. 현실은...

타입은 엄밀하다... 나는 덜엄밀하다...

그래도 확실히 좋은 점은 있다 :

  • 자동 완성으로 작성할 수 있는 코드가 많이 늘었다는 것
  • 내가 무슨 코드를 작성하고있는지 훨씬 면밀히 파악하게 된다는 것

확실히 새기게 된 점도 있다 :

  • 코드는 위에서 아래로, 왼쪽에서 오른쪽으로 읽어나가야 한다는 것
  • 에러 로그에 (대부분)(아마)답이 있다는 것

아무튼 그렇게 시행착오를 겪으면서 과제를 진행했다. 과제 내용을 간추리면 다음과 같다.

TypeScript로 React To-do list 앱을 작성하되, 다섯가지 단계중 선택해서 시행하시오.

  • 레벨1 : React 이용 Todolist
  • 레벨2 : RTK 이용 Todolist
  • 레벨3 : RTK + json-server 이용 Todolist
  • 레벨4 : RTK + redux thunk 이용 Todolist
  • 레벨5 : RTK + react-query 이용 Todolist

당연히 다 할 생각이었다. 1단계에서는 무지 애를 먹고 2단계에서는 조금 덜 먹고, 3단계에서는 그것보다 조금 덜 먹고... 그래서 4단계에도 쉽게 마무리지을 수 있을거라고 생각했는데,

Argument of type 'AsyncThunkAction<Todo[], void, AsyncThunkConfig>' is not assignable to parameter of type 'UnknownAction'.

이 에러 로그가 내 발목을 잡았다. 해당 에러 로그는 Thunk로 작성한 액션을 Dispatch의 인자로 넣어서 호출하는 코드에서 발생한 것인데, 결론적으로는 useDispatch 훅을 사용해 함수를 가져올때 타입을 지정해주지 않아서 Thunk Action이 실행될 수 없었던 것이었다.

남의 코드는 주의깊게 가져오자. 공식 문서라 하더라도...

레벨3를 완성한 코드베이스에 redux-thunk를 적용하기 위해 Redux 도큐먼트의 TypeScript Quick Start 페이지를 참고해 configStore.ts 파일과 todo.slice.ts 파일을 손봤다. 그리고 Thunk 비동기 함수들을 작성하고 export했다. 이제 useTodos 커스텀 훅에서 호출하는 기존 Redux Action들을 Thunk Action으로 교체하기만 하면 완성이었다. 그럴 터였다...

/*
	useTodos.ts
	=> state를 변경해야 할 때 dispatch(action(params)) 로 일일히 호출해줘야하는 것과 그 때마다 dispatch 를 선언해줘야 하는 부분이 번거로워서 만든 custom hook. 그냥 호출하면 되는 형태로 return 하기 때문에 필요한 것만 꺼내다 사용하면 된다.

*/
import { useDispatch } from 'react-redux';
import { Todo } from '../App';
import {
  addTodoThunk,
  deleteTodoThunk,
  fetchTodoThunk,
  updateTodoThunk,
} from '../redux/modules/todo.slice';

export default function useTodos() {
  const dispatch = useDispatch();

  const fetchTodos = () => {
    dispatch(fetchTodoThunk());
  };

  const addTodo = (todo: Todo) => {
    dispatch(addTodoThunk(todo));
  };

  const toggleCompleteTodo = (id: string) => {
    dispatch(updateTodoThunk(id));
  };

  const deleteTodo = (id: string) => {
    dispatch(deleteTodoThunk(id));
  };

  return { fetchTodos, addTodo, toggleCompleteTodo, deleteTodo };
}

이런 식으로 useTodos.ts 파일을 작성하고 저장을 했더니 위에서 봤던

Argument of type 'AsyncThunkAction<Todo[], void, AsyncThunkConfig>' is not assignable to parameter of type 'UnknownAction'.

이 로그가 발생했다. 에러 로그를 열심히 읽는 편이고 거기서 곧잘 답을 찾았는데, TypsScript 를 적용하면서 만나는 에러 로그는 너무 낯설어서 무슨 말인지 잘 모르는채로 코드를 한참 노려봤다. 내가 이해한 바 에러 로그는 다음과 같았다.

AsyncThunkAction<Todo[], void, AsyncThunkConfig>는 Thunk 액션의 타입이다. 그런데 이 타입을 받아들이는 곳(dispatch)에서는 UnknownAction이라는 다른 타입의 parameter를 기대하고 있는 것이다.

간추려 말하자면 dispatch 가 모르는 타입을 인자로 받고 있다는 것이다. 헌데 나는 이 부분을 잘못 이해했다. 내가 이해한 바는

아! dispatch 함수에 전달하는 Thunk 액션이 문제구나!

였다. 그래서 애꿎은 Thunk 액션 정의부만 노려보고 있었다. 하지만 아무리 봐도 타입을 지정해 줄 곳은 다 지정해줬고. 그 외에는 추론이 가능한 부분들인데, 이게 왜 에러를 발생시키는지 모르겠는 것이다. 한참을 노려본 끝에 '여기에는 문제가 없다. 다른 곳에 문제가 있다.' 라는 결론을 내리고 dispatch 로 타겟을 바꿔 노려보기 시작했다. 근데 이 부분에는 문제가 있으면 안 된다! 그도 그럴것이 공식 문서에서 그대로 가져온 코드이니까.

그렇게 또 한참을 노려다보다가 useTodos.ts 커스텀 훅을 보는데 아뿔싸.

const dispatch = useAppDispatch();
// 위와 같이 작성했어야 했는데
const dispatch = useDispatch();
// useDispatch 를 호출하고 있는 것이었다...

여기서 useAppDispatch

// redux/config/configStore.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';

export const store = configureStore({
  reducer: rootReducer,
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// hooks/rtkHooks.ts
import { useDispatch } from 'react-redux';
import { AppDispatch } from '../redux/config/configStore';

export const useAppDispatch: () => AppDispatch = useDispatch;

위의 코드에서 알 수 있듯

  1. dispatch 의 정확한 타입 값을 가지고있는
  2. AppDispatch 타입의 함수를 반환하는 커스텀 훅

이다. 여기서 1번이 중요하다. useAppDispatch 가 아닌 useDispatch 훅은 디스패치 함수의 타입 값을 가지고있지 않기 때문에 에러가 발생한 것이다. 이 코드만 교체해주니 에러가 해결되었다. 그리고 나는 네시에 잤다.

다시 한 번 강조

에러 코드 안에 답이 있다!

그리고

코드 가져올때에는 꼼꼼하게 읽고 가져오자!

profile
웹 프론트엔드 개발을 익히고 있습니다.

0개의 댓글