Skip to content

성급한 추상화 (feat. useCustomMutation)

Znero edited this page Dec 1, 2024 · 1 revision

성급한 추상화

shell api에 대해 mutation 로직을 작성하다가 상당히 많은 코드가 반복된다는 것을 깨달았습니다. 이에 뮤테이션 로직을 따로 분리하면 좋지 않을까 생각해서 해당 로직을 분리해 custom mutation hook으로 만들었습니다.

처음에는 반복적으로 로직을 작성하지 않아도 되어 편리하다고 생각했습니다, 다만 이 custom mutation hook 은 쓸수록 불편한 점이 생겨났고, 추상화를 통해 얻은 장점이 적다는 것을 깨달았습니다.

일단 제가 작성한 custom mutation hook 은 아래와 같습니다.

import { useMutation, UseMutationOptions, useQueryClient } from 'react-query'

// 이전 데이터를 저장, 에러발생시 롤백
type MutationContext<TData> = {
  previousData: TData[] | undefined
}

// 커스텀 훅 정의: useCustomMutation
// - TData: 변환될 데이터 타입
// - TVariables: 뮤테이션 함수가 받을 변수의 타입 (TData 확장)
export default function useCustomMutation<TData, TVariables extends TData>(
  mutationFn: (variables: TVariables) => Promise<TData>,
  queryKey: string | string[],
  options?: UseMutationOptions<TData, Error, TVariables, MutationContext<TData>>
) {
  const queryClient = useQueryClient()

  return useMutation<TData, Error, TVariables, MutationContext<TData>>(
    mutationFn,
    {
	    //이전 데이터를 저장
      onMutate: () => {
        const previousData = queryClient.getQueryData<TData[]>(queryKey)
        return { previousData }
      },
      // 뮤테이션 성공 시
      onSuccess: () => {
        queryClient.invalidateQueries(queryKey)
      },
      //뮤테이션 실패 시 이전 데이터를 복원
      onError: (error, _, context) => {
        if (context?.previousData) {
          queryClient.setQueryData(queryKey, context.previousData)
        }
        console.error(error)
      },
      ...options, // 사용자 정의 옵션
    }
  )
}



성급한 추상화의 결과

타입 정의의 불편함

일부 api의 경우 TDataTVariables의 타입이 동일해서 타입을 명시적으로 지정할 필요가 없어서 사용하기 편리했습니다.

export function useAddShell() {
  return useCustomMutation(addShell, QUERY_KEYS.SHELLS)
}

다만 TDataTVariables의 타입이 다른 api에서 해당 훅을 사용하려고 하니 일일히 타입을 지정해줘야 해 불편했습니다.

import useCustomMutation from './useCustomMutation'
import { QUERY_KEYS } from './constants'
import { RecordResultType, RecordToolType } from './types'

export default function useAddRecord() {
  return useCustomMutation<RecordResultType, RecordToolType>(
    addRecord,
    QUERY_KEYS.RECORD 
  )
}

제한된 범용성

현재는 로직이 단순해서 쿼리키가 정적으로만 사용되고 있지만, 쿼리키가 동적으로 생성되어야할 경우가 있을 수 있습니다. 특정 조건에서만 쿼리를 무효화하는 등 다양한 로직이 존재할 수 있는데 해당 custom mutation 훅은 그런 로직을 처리할 수 없기에 범용성이 낮았습니다.




결론

반복적인 코드를 줄이기 위해 커스텀 뮤테이션 훅을 만들었지만, 추상화의 범용성과 유연성이 부족했습니다. 타입 정의의 불편함, 동적 로직 처리의 한계 등으로 인해, 오히려 사용성이 떨어지는 결과를 초래했습니다.


교훈

  • 반복적으로 코드를 작성하면서 충분히 공통점을 파악한 뒤, 이를 기반으로 추상화하기
  • 추상화를 하기 전에 추상화의 이점을 생각해보기

Clone this wiki locally