안녕하세요. 백엔드 개발자 민경찬입니다.
오늘은 Tanstack/query
라이브러리에서 제공하는 useMutation
사용 주의 사항에 대해서 소개해볼까합니다.
Tanstack/query
라이브러리에서 제공하는 기술로, 서버에 데이터를 생성, 업데이트, 삭제 등의 비동기 요청을 수행할 때 사용하는 훅입니다.
const { mutate } = useMutation({
mutationFn: async (someVal: string) => {},
onMutate: (someData) => someData,
onError: (_error, _variables, context) => {},
onSettled: (_data, _error, _variables, context) => {},
});
useMutation
은 정말 놀라운 Typescript 호환성을 보여줍니다.
mutationFn
과 onMutate
가 어떤 값을 리턴하냐에 따라 onError
와 onSettled
의 파라미터 타입이 자동으로 추적됩니다.
정말 훌륭한 DX입니다.
하지만 Typescript를 극한으로 활용한 만큼, 일부 부작용이 있습니다.
예를 들어볼게요.
아래 처럼 onMutate
가 { data: string }
타입을 리턴한다면 context
는 자동으로 그 타입으로 추적됩니다.
const { mutate } = useMutation({
mutationFn: async (someVal: string) => {},
onMutate: (someData) => ({ data: "some data" }), // { data: string }
onError: (_error, _variables, context) => {
context?.data; // data가 잡힘
},
onSettled: (_data, _error, _variables, context) => {},
});
그러나!
onMutate
를 onError
보다 아래에 쓰게 된다면 어떻게 될까요?
const { mutate } = useMutation({
mutationFn: async (someVal: string) => {},
onError: (_error, _variables, context) => {
context?.data; // Error: Property 'data' does not exist on type '{}'.
},
onMutate: (someData) => ({ data: "some data" }), // { data: string }
onSettled: (_data, _error, _variables, context) => {},
});
에러를 마주하게 됩니다. 이때 context
는 더 이상 { data: string }
타입이 아닌, unknown
이기 때문입니다.
이는 Typescript 구현 자체가 이런 방식으로 되어있기에 어쩔 수가 없습니다.
관련 내용: https://github.com/microsoft/TypeScript/issues/53018#issuecomment-1518558545
한 번 잘못 마주하면 정말 오랜 시간을 낭비하게 될 겁니다.
타입이 unknown
으로 잡히는데 아 Typescript 특성 때문에 타입이 uknown으로 추적되니 onMutate를 위로 올려야겠구나!
라고 생각할 수 있을까요.
저는 힘들 것 같습니다.
하지만 이런 문제는 비단 useMutation
만의 문제가 아닙니다. useInfiniteQuery
에서도 같은 문제가 있죠.
@Tanstack/query
에서는 이런 문제를 진작 인지하고 있었고 이를 방지하기 위하여 ES-Lint 플러그인을 이용하고 있습니다.
ES-Lint 의 기능을 활용하여 속성의 순서가 어긋나면 빨간줄을 띄워주는 것이죠!
https://tanstack.com/query/latest/docs/eslint/eslint-plugin-query
useInfiniteQuery
는 ES-Lint 규칙이 존재하지만 useMutation
은 존재하지 않습니다.
@Tanstack/query
이슈에도 올라와있어요.
그래서 저는 기여를 도전해보기로 했어요.
이미 property order를 확인하는 ES-Lint 플러그인이 라이브러리 내부에 구현되어 있었기에 따라 구현하는 것은 어렵지 않았어요.
테스트 코드도 금방 작성할 수 있었습니다.
그러나, infinite-query-property-order
와 로직이 너무 겹친다는 피드백을 받았습니다.
이 두 개의 로직을 하나로 합쳐 유틸 함수로 만들었고 빠르게 다시 PR을 올려봤습니다.
올린지 얼마 되지 않고 머지가 되더라구요.
ES-Lint 플러그인이 원하는대로 작동하는지에 관련한 테스트도 신기하더라구요.
제가 알던 Jest 방식의 테스트와 달라 인상이 깊었습니다.
@Tanstack/query
가 어떻게든 DX를 챙기려는 것도 매우 인상이 깊었어요.
ES-Lint 플러그인 잘 세팅해서 개발해야겠다는 생각도 들더라구요.
여러분들도 ES-Lint 세팅하고 개발하세요. :)
도움주신 인제님께 다시 한 번 감사 인사드립니다!
👏👏👏👏👏