react todo app 2차 리팩토링 1/2

권현경·2022년 11월 17일
1
post-custom-banner

1. 라우팅

기존에는 react-router-domBrowserRouter 컴포넌트를 이용하여 작업하였다. 그런데 createBrowserRouter라는 함수를 알게되었다. createBrowserRoutereact-router-dom 공식 문서에서 new라는 뱃지를 붙여놓고 가장 추천하고 있는 최신 방식이다.

createBroswerRouter를 사용하면 페이지의 연결 구조를 한눈에 살펴보기 쉽고 여러 가지 옵션을 제공해준다. 다음에 살펴볼 비동기처리에서도 나오겠지만 요즘은 에러, 데이터 fetching 등을 UI와 분리해서 작업하게 해주는 도구들이 많이 나오는 것 같다.

loader와 action을 사용해서 데이터 fetching과 mutation을 UI 컴포넌트와 분리시켜줄 수 있는 것 같다. 나는 loader에 토큰 검사하는 로직을 넣어서 로그인이 필요한 컴포넌트를 보호해줄 수 있게 했다. 또 logout 같이 UI 컴포넌트가 따로 필요없는 경로에도 loader를 넣어주었다.

그리고 TodoList의 children으로 Detail 페이지를 넣어주어서 todoList의 loader는 공유하되 컴포넌트를 분리할 수 있게 만들었다. (원래는 list 페이지 안에 상세 페이지도 함께 있었다.)

최종 결과물은 아래와 같다.Create Edit Delete Detail 같은 CRUD 컴포넌트들을 라우터로 분리해주었다.

const router = createBrowserRouter([
  {
    path: MAIN_URL,
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, loader: mainLoader },
      {
        path: LOGOUT_URL,
        loader: logoutLoader,
      },
      {
        path: LOGIN_URL,
        element: <Login />,
      },
      {
        path: SIGN_UP_URL,
        element: <SignUp />,
      },
      {
        path: TODO_LIST_URL,
        element: <TodoIndex />,
        loader: todoListLoader,
        children: [
          { path: TODO_CREATE_URL, element: <Create /> },
          {
            path: TODO_DETAIL_URL,
            element: (
              <React.Suspense
                fallback={
                  <Skeleton variant="rectangular" width={300} height={220} />
                }
              >
                <Detail />
              </React.Suspense>
            ),
            children: [
              {
                path: TODO_DELETE_DETAIL_URL,
                element: <Delete />,
              },
              {
                path: TODO_EDIT_DETAIL_URL,
                element: <Edit />,
              },
            ],
          },
        ],
      },
    ],
  },
]);

2. useMutation 제대로 사용하기

useMutation을 제대로 사용하고 있지 않다는 생각이 들어서 먼저 react query 공식 블로그인 tkdodo의 블로그를 보았다.

useMutation과 mutate의 콜백 차이

useMutation의 onSuccess 콜백과 mutate의 onSuccess 콜백의 차이를 몰랐었는데 블로그를 보고 알수 있었다.

useMutation의 callback

  • mutate의 callback 이전에 실행됨
  • useMutation의 callback에는 반드시 실행되어야 하는 로직을 넣는 것 추천

mutate의 callback

  • mutation이 끝나기전에 컴포넌트가 언마운트되면 실행되지 않음
  • mutate의 callback에는 사용자가 도중에 페이지를 이동할수도 있다는 것을 감안한채로 UI와 관련된 로직(ex 토스트, navigate)을 넣는 것 추천

mutation 이후 후처리

이전에는 mutation 후에 리스트를 다시 받아오기 위해 refetch를 받아와서 작업하고 있었는데 공식 문서에서 추천한 세가지 방법이 있었다.

  1. invalidateQueries
    mutation이 성공하면 query key를 사용하여 원하는 데이터를 refetch 할 수 있게 해준다.
 const queryClient = useQueryClient();
 
 onSuccess: () => {
     queryClient.invalidateQueries(["todos"]);
  },

2.Direct Updates
POST나 PUT 같은 API 요청을 했을 때 response로 받아오는 값에 data가 있다면 refetch를 하지 않고 바로 업데이트 해줄 수도 있다.

const useUpdateTitle = (id) => {
  const queryClient = useQueryClient()

  return useMutation(
    (newTitle) => axios
      .patch(`/posts/${id}`, { title: newTitle })
      .then(response => response.data),
    {
      // 💡 response of the mutation is passed to onSuccess
      onSuccess: (newPost) => {
        // ✅ update detail view directly
        queryClient.setQueryData(['posts', id], newPost)
      },
    }
  )
}
  1. Optimistic Updates
    우리는 수정이나 등록을 할때 이미 업데이트될 데이터를 가지고 있다. 그 데이터를 이용해서 수동으로 업데이트를 하여 사용자에게 바로 업데이트가 된 것처럼 수정된 내용을 보여줄 수 있다. 네트워크가 아주 느린 곳에 있는 사용자들에게 좋은 사용자 경험을 제공해줄 수 있지 않을까 싶다.
    (단, 이건 요청이 무조건 실패하지 않는다는 전제하에 할 수 있는 업데이트 방법이다.)

일단 두번째 방법은 요청 후 response 값에서 따로 데이터를 받아오고 있지 않기 때문에 제외했다. 그리고 세번째 방법보다는 첫번째 방법이 더 안전하게 데이터를 업데이트해주고 사용자에게 혼란을 주지 않을 수 있다 생각해서 invalidateQueries를 사용하기로 결정했다.

profile
프론트엔드 개발자
post-custom-banner

0개의 댓글