Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] 쿼리 리팩터링 #953

Merged
merged 4 commits into from
Nov 13, 2024
Merged

[FE] 쿼리 리팩터링 #953

merged 4 commits into from
Nov 13, 2024

Conversation

anttiey
Copy link
Contributor

@anttiey anttiey commented Nov 6, 2024

연관된 이슈

구현한 기능

  • 전반적인 쿼리 로직을 리팩터링

상세 설명

파일 구조

  • 데이터를 가져오는 GET 요청일 경우 useGet*- 와 같이 네이밍된 파일 아래에 위치합니다.
  • 데이터를 수정하는 POST, PATCH, DELETE 요청일 경우 useMutate*- 와 같이 네이밍된 파일 아래 위치합니다.
  • 데이터는 반드시 명확한 변수명으로 반환합니다.
  • 각 mutation은 반드시 명확한 함수명으로 반환합니다.

예시 (useGetTodos)

const useGetTodos = (accessCode: string) => {
  const { data } = useQuery({
    queryKey: [QUERY_KEYS.GET_TODOS],
    queryFn: () => getTodos(accessCode),
  });

  return { todos: data || [] };
};

export default useGetTodos;

예시 (useMutateTodos)

const useMutateTodos = () => {
  const queryClient = useQueryClient();

  const { addToast } = useToastStore();

  const { mutate: addTodosMutation } = useMutation({
    mutationFn: addTodos,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_TODOS] }),
    onError: (error) => addToast({ status: 'ERROR', message: error.message }),
  });

  const { mutate: updateContentsMutation } = useMutation({
    mutationFn: updateContents,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_TODOS] }),
    onError: (error) => addToast({ status: 'ERROR', message: error.message }),
  });

  const { mutate: updateOrderMutation } = useMutation({
    mutationFn: updateOrder,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_TODOS] }),
    onError: (error) => addToast({ status: 'ERROR', message: error.message }),
  });

  const { mutate: updateCheckedMutation } = useMutation({
    mutationFn: updateChecked,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_TODOS] }),
    onError: (error) => addToast({ status: 'ERROR', message: error.message }),
  });

  const { mutate: deleteTodoMutation } = useMutation({
    mutationFn: deleteTodo,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_TODOS] }),
    onError: (error) => addToast({ status: 'ERROR', message: error.message }),
  });

  return {
    addTodosMutation,
    updateContentsMutation,
    updateOrderMutation,
    updateCheckedMutation,
    deleteTodoMutation,
  };
};

export default useMutateTodos;

컨벤션 및 주의할 점

  • mutateAsync 를 사용하지 않습니다.
  • 만약 mutate 후 별도의 성공 로직이 필요하다면 컴포넌트 안에서 onSuccess 속성으로 추가합니다.
  • 앞으로 쿼리 로직을 새로 추가한다면 기존의 로직과 같은 방식을 따릅니다.

변경 전

export const useAddCategory = (onSuccess?: () => void) => {
  const queryClient = useQueryClient();

  const { addToast } = useToastStore();

  return useMutation({
    mutationFn: addCategory,
    onSuccess: () => {
      onSuccess && onSuccess();
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_CATEGORIES] });
    },
    onError: (error) => addToast({ status: 'ERROR', message: error.message }),
  });
};
const handleAddCategorySubmit = (event: React.FormEvent) => {
  event.preventDefault();

  if (status === 'ERROR') return;

  addCategory.mutateAsync({ category: value, accessCode }).then(() => resetValue());
};

변경 후

const { mutate: addCategoryMutation } = useMutation({
  mutationFn: addCategory,
  onSuccess: () => queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_CATEGORIES] }),
  onError: (error) => addToast({ status: 'ERROR', message: error.message }),
});
const handleAddCategorySubmit = (event: React.FormEvent) => {
  event.preventDefault();

  if (status === 'ERROR') return;

  addCategoryMutation({ category: value, accessCode }, { onSuccess: resetValue });
};
...
  • mutateAsync 는 프로미스에 대한 제어권을 개발자에게 양도하는 것입니다. 즉, onSuccess, onError 등의 처리를 모두 개발자가 해야 한다는 뜻과 같습니다.
  • mutate 는 같은 API가 여러 번 호출되었을 때 마지막 API 호출만 처리하지만, mutateAsync 는 그렇지 않습니다.
  • 컴포넌트 안에서 onSuccess 속성을 추가하면 쿼리 훅에서 설정해 둔 onSuccess 속성이 실행된 후 추가로 실행됩니다.

Copy link
Contributor

@dle234 dle234 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생했습니다!! query 파일들이 통일돼서 마음이 편하네요👍
mutation 구조 부분에서 조금 생각이 다른 부분이 있어서 리뷰 달아놨어요 확인 부탁해요!

});

return {
addPairRoomMutation,
Copy link
Contributor

@dle234 dle234 Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutate 의 경우에는 suspense 로 로딩 처리를 하기보다 isPending 으로 개별 로딩 처리하는게 더 좋을 것 같다고 생각해요. 그래서 hook처럼 한번에 export 하기보다 개별적으로 export 하는게 더 나을 것 같다고 생각했어요!

export deletePairRoomMutation = useMutation({
    mutationFn: deletePairRoom,
... 생략 ...

이렇게 한 파일에서 개별적으로 export 한 후 사용처에서
deletePairRoomMut�ation.mutate, deletePairRoomMutation.isPending 등으로 사용하는건 어떨까요?

그리고 이거는 개인적인 의견이라 반영 안해도 상관 없는데 mutation 을 모아두는 파일 이름이 useMutate- 이고 mutate 의 이름은 -mutation 인게 조금 어색한 것 같다고 생각했어요.
그리고 useGet- 의 경우에도 hook 과 네이밍의 차이점이 없어서 파일 경로가 없다면 헷갈릴수도 있을 것 같았어요.
그래서 mutation 파일 이름은 use~Mutation으로 하고 query 파일 이름은 use~Query 로 하는게 더 파악하기 쉽다고 생각했는데 어떻게 생각하시나요?
(근데 네이밍은 팀 내에서 통일만 하면 된다고 생각해서 이전것도 괜찮습니다! )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금도 isPending 으로 개별 로딩 처리되어 있는 상태이긴 합니다! (제가 잘못 이해했다면 알려 주세요)

const { mutate: deletePairRoomMutation, isPending: isDeletePairRoomPending } = useMutation({
  mutationFn: deletePairRoom,
  onSuccess: () => {
    addToast({ status: 'SUCCESS', message: '페어룸이 삭제되었습니다.' });
    return queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_MY_PAIR_ROOMS] });
  },
  onError: () => addToast({ status: 'ERROR', message: '페어룸 삭제에 실패했습니다.' }),
});
if (isDeletePairRoomPending) {
  return (
    <S.Layout>
      <Spinner />
    </S.Layout>
   )
}

따라서 해시가 제시한 방식와 제가 설계한 방식의 차이점은 mutation 을 그대로 내보내느냐, 구조 분해 할당을 해서 내보내느냐의 차이에 있겠네요. 저는 이왕 커스텀 훅으로 설계했으니 다른 커스텀 훅과 동일하게 사용할 변수만 구조 분해 할당으로 내보내는 게 데이터 은닉 관점에서 더 좋겠다고 생각했어요 🙂

네이밍 관련해서는 팀 전체가 모였을 때 함께 고민해 봅시다 🙌🏻

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제 생각에 파일 네이밍의 경우 use~Mutation, use~Query 가 더 좋은 것 같아요!
지금은 훅과 쿼리가 네이밍이 비슷해 종종 헷갈리는 일이 있어요 😭
구조분해할당의 경우에는 애초에 저희가 훅으로 설계했다보니 파슬리의 말대로 구조 분해 할당으로 내보내는 게 밖에서 사용할 때 잘못 사용할 경우의 수가 적어 좋다고 생각합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵! 그러면 네이밍 수정해서 다시 요청드릴게요 🙇🏻‍♀️

Copy link
Contributor

@greetings1012 greetings1012 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다 👍
깔끔하게 리팩터링 해 주셨네요!

@anttiey anttiey merged commit a2e8a5d into FE/dev Nov 13, 2024
1 of 2 checks passed
@anttiey anttiey deleted the FE/feature/#943 branch November 13, 2024 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚙️ refactor 리팩터링 작업
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants