[React Query] v4 버전 달라진 점 (릴리즈 노트 정리)
[React Query] v4 버전 릴리즈 노트 정리
React Query가 v4 버전으로 업데이트되었다. 회사 프로젝트에서도 v4로(v3.39.2 → 4.24.6) 마이그레이션 하게 되었는데, 그 과정에서 달라진 점을 정리해 보았다.
Codemod
- 마이그레이션을 도와주는 Codemod 가 있음
- The codemod is a best efforts attempt to help you migrate the breaking change. Please review the generated code thoroughly! Also, there are edge cases that cannot be found by the code mod, so please keep an eye on the log output.
- jsx/js 일 경우
npx jscodeshift ./path/to/src/ \\
--extensions=js,jsx \\
--transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
- tsx/ts 인 경우
npx jscodeshift ./path/to/src/ \\
--extensions=ts,tsx \\
--parser=tsx \\
--transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
주의사항
- codemod는 코드를 변경시키므로 실행 후 prettier나 eslint 로 formatting이 필요
- codemod 는 import만 변경시켜 준다. 그러므로 devtools package는 수동으로 설치해야 한다.
- yarn berry에서 node_modules 폴더가 압축되어 있기 때문에 사용할 수 없었음ㅠ
Query Keys (and Mutation Keys) need to be an Array
Query key (혹은 Mutation Key)가 배열로 변경되어야 한다.
v3에서 key는 String | Array 둘 다 가능했다. 하지만 v4에서는 Array 형태만 가능
- useQuery('todos', fetchTodos)
+ useQuery(['todos'], fetchTodos)
idle state가 제거됨
오프라인 지원을 개선하기 위해 새로운 fetchStatus가 도입되면서, 동일한 상태를 더 잘 캡처하는 fetchStatus: 'idle'로 인해 idle state는 무의미해짐. 자세한 내용은 두 가지 상태가 다른 이유를 참조하세요. 이는 주로 아직 데이터가 없는 비활성화된 쿼리에 영향을 미치며, 이전에는 유휴 상태였기 때문이다.
- status: 'idle'
+ status: 'loading'
+ fetchStatus: 'idle'
disabled queries
이 변경 사항으로 인해 disabled queries쿼리 (일시적으로 비활성화된 쿼리도 포함)는 로딩 상태에서 시작됩니다. 특히 로딩 스피너를 표시할 시기를 알 수 있는 좋은 플래그를 사용하여 마이그레이션을 더 쉽게 하려면 isLoading 대신 isInitialLoading을 확인하면 됩니다.
new API for useQueries
이제 useQueries hook은 queries prop가 있는 객체를 input으로 받습니다. queries prop의 값은 쿼리 배열입니다(이 배열은 v3에서 useQueries에 전달된 것과 동일합니다).
- useQueries([{ queryKey1, queryFn1, options1 }, { queryKey2, queryFn2, options2 }])
+ useQueries({ queries: [{ queryKey1, queryFn1, options1 }, { queryKey2, queryFn2, options2 }] })
Undefined is an illegal cache value for successful queries
undefined을 반환하여 업데이트에서 벗어날 수 있게 하려면 undefined을 잘못된 캐시 값으로 만들어야 했습니다. 이는 초기 Data 함수에서 정의되지 않은 값을 반환해도 데이터가 설정되지 않는 등 다른 리액트 쿼리 개념과도 일치합니다.
또한 queryFn에 로깅을 추가하여 Promise<void>를 생성하는 것은 쉬운 버그입니다:
useQuery(['key'], () => axios.get(url).then(result => console.log(result.data)))
이제 type level에서는 허용되지 않으며, 런타임에 undefined은 failed Promise로 변환되어 오류가 발생하고 개발 모드에서 콘솔에도 기록됩니다.
Queries and mutations, per default, need network connection to run
online/offline 지원에 대한 새로운 기능 공지와 네트워크 모드에 대한 전용 페이지를 읽어보세요.
React Query는 프로미스를 생성하는 모든 것에 사용할 수 있는 비동기 상태 관리자이지만, 데이터 불러오기 라이브러리와 함께 데이터 불러오기에 가장 자주 사용됩니다. 따라서 기본적으로 네트워크에 연결되어 있지 않으면 queries, mutations가 paused 됩니다. 이전 동작을 선택하려면 queries, mutations 모두에 대해 전역적으로 networkMode: offlineFirst를 설정하면 됩니다:
new QueryClient({
defaultOptions: {
queries: {
networkMode: 'offlineFirst',
},
mutations: {
networkMode: 'offlineFirst',
},
},
})
notifyOnChangeProps property no longer accepts "tracked" as a value
notifyOnChangeProps 옵션은 더 이상 "**tracked**" 값을 허용하지 않습니다. 대신, useQuery는 기본적으로 속성을 추적합니다. notifyOnChangeProps: "tracked"를 사용하는 모든 쿼리는 이 옵션을 제거하여 업데이트해야 합니다.
쿼리가 변경될 때마다 다시 렌더링하는 v3 기본 동작을 에뮬레이트하기 위해 쿼리에서 이를 우회하려는 경우, 이제 notifyOnChangeProps에서 기본 smart tracking optimization를 opt-out 하기 위해 "all" 값을 허용합니다.
notifyOnChangePropsExclusion has been removed
v4에서 notifyOnChangeProps는 정의되지 않은 대신 v3의 "tracked" 동작으로 기본 설정됩니다. 이제 "tracked"이 v4의 기본 동작이므로 이 구성 옵션을 포함하는 것이 더 이상 의미가 없습니다.
Consistent behavior for cancelRefetch
cancelRefetch 옵션은 query를 필수적으로 fetch 하는 모든 함수에 전달할 수 있습니다:
- queryClient.refetchQueries
- queryClient.invalidateQueries
- queryClient.resetQueries
- refetch returned from useQuery
- fetchNextPage and fetchPreviousPage returned from useInfiniteQuery
fetchNextPage 및 fetchPreviousPage를 제외하고는, 이 flag가 기본값이 false로 설정되어 일관성이 없고 잠재적으로 문제가 될 수 있었습니다: 이전에 slow fetch가 이미 진행 중이었다면 refetch를 건너뛰었기 때문에 변경 후 refetchQueries 또는 invalidateQueries를 호출해도 최신 결과를 얻지 못할 수 있습니다.
사용자가 작성한 일부 코드에 의해 쿼리가 활발하게 다시 가져오는 경우, 기본적으로 가져오기를 다시 시작해야 한다고 생각합니다.
이것이 바로 위에서 언급한 모든 메서드에서 이 flag의 기본값이 true로 설정된 이유입니다. 또한 대기하지 않고 refetchQueries를 연속으로 두 번 호출하면 이제 첫 번째 가져오기를 취소하고 두 번째 가져오기로 다시 시작한다는 의미이기도 합니다:
queryClient.refetchQueries({ queryKey: ['todos'] })
// this will abort the previous refetch and start a new fetch
queryClient.refetchQueries({ queryKey: ['todos'] })
**cancelRefetch:false**를 명시적으로 설정하여 이 동작을 옵트아웃할 수 있습니다:
queryClient.refetchQueries({ queryKey: ['todos'] })
// this will not abort the previous refetch - it will just be ignored
queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })
Note: query mounts 또는 window focus refetch 등으로 인해 자동으로 트리거 되는 fetch 동작에는 변화가 없습니다.
Query Filters
**query filter**는 query와 일치하는 특정 조건을 가진 객체입니다. 지금까지 필터 옵션은 대부분 boolean flag 조합이었습니다. 하지만 이러한 flag를 조합하면 불가능한 상태가 발생할 수 있습니다. 구체적으로 설명하자면
active?: boolean
- When set to true it will match active queries.
- When set to false it will match inactive queries.
inactive?: boolean
- When set to true it will match inactive queries.
- When set to false it will match active queries.
이러한 flag는 상호 배타적이기 때문에 함께 사용하면 잘 작동하지 않습니다. 두 플래그 모두 거짓으로 설정하면 설명에 따라 모든 쿼리가 일치하거나 쿼리가 일치하지 않을 수 있는데, 이는 말이 되지 않습니다.
v4에서는 이러한 필터가 단일 필터로 결합되어 의도를 더 잘 보여줄 수 있게 되었습니다:
필터는 기본적으로 **all**로 설정되며 active 쿼리 또는 inactive 쿼리만 일치하도록 선택할 수 있습니다.
refetchActive / refetchInactive
queryClient.invalidateQueries 유사한 플래그가 두 개 더 있습니다:
refetchActive: Boolean
- Defaults to true
- When set to false, queries that match the refetch predicate and are actively being rendered
via useQuery and friends will NOT be refetched in the background, and only marked as invalid.
refetchInactive: Boolean
- Defaults to false
- When set to true, queries that match the refetch predicate and are not being rendered
via useQuery and friends will be both marked as invalid and also refetched in the background
같은 이유로 이 두 가지 기능도 통합되었습니다:
- active?: boolean
- inactive?: boolean
+ refetchType?: 'active' | 'inactive' | 'all' | 'none'
이 flag의 기본값은 refetchActive가 true로 설정되어 있기 때문에 active으로 설정됩니다. 즉, invalidateQueries에 refetch 하지 않도록 지시하는 방법도 필요하므로 여기에서는 네 번째 옵션(none)도 허용됩니다.
onSuccess is no longer called from setQueryData
이것은 많은 사람들에게 혼란을 주었고, onSuccess 내에서 setQueryData가 호출될 경우 무한 루프를 생성하기도 했습니다. 또한 캐시에서만 데이터를 읽으면 onSuccess가 호출되지 않기 때문에 staleTime과 결합하면 오류가 자주 발생했습니다.
onError 및 **onSettled**와 유사하게, 이제 onSuccess 콜백이 요청이 이루어지고 있는 요청에 연결됩니다. No request -> no callback.
data 필드의 변경 사항을 listen 하고 싶다면, 데이터가 dependency Array의 일부인 **useEffect**를 사용하는 것이 가장 좋습니다. React Query는 구조적 공유를 통해 안정적인 데이터를 보장하기 때문에 백그라운드에서 불러올 때마다 이펙트가 실행되는 것이 아니라 데이터 내 무언가가 변경된 경우에만 이펙트가 실행됩니다.
const { data } = useQuery({ queryKey, queryFn })
React.useEffect(() => mySideEffectHere(data), [data])
persistQueryClient and the corresponding persister plugins are no longer experimenta respectively.
createWebStoragePersistor 및 createAsyncStoragePersistor 플러그인의 이름이 각각 createSyncStoragePersister 및 createAsyncStoragePersister로 변경되었습니다. **persistQueryClient**의 Persistor 인터페이스도 **Persister**로 이름이 변경되었습니다. **this stackexchange**에서 이 변경의 motivation를 확인해 보세요.
이 플러그인은 더 이상 실험적이지 않으므로 가져오기 경로도 업데이트되었습니다:
- import { persistQueryClient } from 'react-query/persistQueryClient-experimental'
- import { createWebStoragePersistor } from 'react-query/createWebStoragePersistor-experimental'
- import { createAsyncStoragePersistor } from 'react-query/createAsyncStoragePersistor-experimental'
+ import { persistQueryClient } from '@tanstack/react-query-persist-client'
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
The cancel method on promises is no longer supported
라이브러리에서 query cancellation를 지원하기 위해 프로미스에 cancel 함수를 정의할 수 있게 해 주던 old cancel method는 제거되었습니다. 쿼리 취소를 위해서는 내부적으로 AbortController API를 사용하고 쿼리 취소를 지원하기 위해 쿼리 함수에 대한 AbortSignal instance를 제공하는 newer API(v3.30.0에 도입됨)를 사용하는 것이 좋습니다.
Typescript
Types now require using TypeScript v4.1 or greater
Supported Browsers
v4부터 React Query는 최신 브라우저에 최적화되었습니다. 보다 현대적이고 성능이 우수하며 더 작은 번들을 생성하기 위해 브라우저 목록을 업데이트했습니다.
Chrome >= 73
Firefox >= 78
Edge >= 79
Safari >= 12.0
iOS >= 12.0
opera >= 53
setLogger is removed
setLogger를 호출하여 logger를 전역적으로 변경할 수 있었습니다. v4에서는 이 함수가 QueryClient를 생성할 때 선택적 필드로 대체됩니다.
- import { QueryClient, setLogger } from 'react-query';
+ import { QueryClient } from '@tanstack/react-query';
- setLogger(customLogger)
- const queryClient = new QueryClient();
+ const queryClient = new QueryClient({ logger: customLogger })
No default manual Garbage Collection server-side
v3에서 React Query는 기본값인 5분 동안 쿼리 결과를 캐시 한 다음 해당 데이터를 수동으로 garbage collect 합니다. 이 기본값은 서버 측 React Query에도 적용되었습니다.
이 수동 garbage collect이 완료될 때까지 기다리는 동안 메모리를 많이 소비하고 프로세스가 중단되었습니다. v4에서는 이제 기본적으로 서버 측 **cacheTime**이 **Infinity**로 설정되어 수동 가비지 수집을 효과적으로 비활성화합니다(요청이 완료되면 NodeJS 프로세스가 모든 것을 지웁니다).
이 변경 사항은 Next.js와 같은 서버 측 React 쿼리 사용자에게만 영향을 줍니다. **cacheTime**을 수동으로 설정하는 경우에는 영향을 받지 않습니다(동작을 미러링하고 싶을 수도 있지만).
ESM Support
이제 React Query는 package.json "exports"를 지원하며 CommonJS 및 ESM 모두에 대해 Node의 기본 해상도와 완벽하게 호환됩니다. 대부분의 사용자에게 큰 변화는 아닐 것으로 예상되지만, 프로젝트에 가져올 수 있는 파일은 공식적으로 지원하는 엔트리 포인트로만 제한됩니다.
Streamlined NotifyEvents
QueryCache를 수동으로 구독하면 항상 QueryCacheNotifyEvent가 발생했지만, MutationCache에서는 그렇지 않았습니다. 동작을 간소화하고 그에 따라 이벤트 이름도 변경했습니다.
QueryCacheNotifyEvent
- type: 'queryAdded'
+ type: 'added'
- type: 'queryRemoved'
+ type: 'removed'
- type: 'queryUpdated'
+ type: 'updated'
**MutationCacheNotifyEvent**는 **QueryCacheNotifyEvent**와 동일한 유형을 사용합니다.
Note: 이는 queryCache.subscribe 또는 mutationCache.subscribe을 통해 수동으로 캐시를 구독하는 경우에만 해당됩니다.
Separate hydration exports have been removed
버전 **3.22.0**에서는 hydration utilities가 React 쿼리 코어로 이동했습니다. v3에서는 여전히 **react-query/hydration**의 old exports를 사용할 수 있었지만, v4에서는 이러한 exports가 제거되었습니다.
- import { dehydrate, hydrate, useHydrate, Hydrate } from 'react-query/hydration'
+ import { dehydrate, hydrate, useHydrate, Hydrate } from '@tanstack/react-query'
Removed undocumented methods from the queryClient, query and mutation
**QueryClient**의 **cancelMutatations**와 executeMutation 메서드는 문서화되지 않았고 내부적으로 사용되지 않았기 때문에 제거했습니다. 이 메서드는 **mutationCache**에서 사용할 수 있는 메서드를 래퍼로 만든 것이기 때문에, 여전히 **executeMutation**의 기능을 사용할 수 있습니다.
- executeMutation<
- TData = unknown,
- TError = unknown,
- TVariables = void,
- TContext = unknown
- >(
- options: MutationOptions<TData, TError, TVariables, TContext>
- ): Promise<TData> {
- return this.mutationCache.build(this, options).execute()
- }
또한 **query.setDefaultOptions**도 사용되지 않아 제거되었습니다. **mutation.cancel**은 실제로 발신 요청을 취소하지 않았기 때문에 제거되었습니다.
The src/react directory was renamed to src/reactjs
이전에는 React Query에 react 모듈에서 가져온 react라는 이름의 디렉터리가 있었습니다. 이로 인해 일부 Jest 구성에 문제가 발생하여 다음과 같은 테스트를 실행할 때 오류가 발생할 수 있었습니다:
TypeError: Cannot read property 'createContext' of undefined
디렉토리의 이름이 바뀌었기 때문에 더 이상 문제가 되지 않습니다.
프로젝트에서 'react-query/react'에서 직접 무언가를 import 하는 경우('react-query'가 아닌), imports를 업데이트해야 합니다:
- import { QueryClientProvider } from 'react-query/react';
+ import { QueryClientProvider } from '@tanstack/react-query/reactjs';
참고
- https://tanstack.com/query/v4/docs/react/guides/migrating-to-react-query-4
'프로그래밍 > React-Query' 카테고리의 다른 글
[React Query] 전역 상태로 사용하기 (0) | 2023.03.31 |
---|