|
| 1 | +# TanStack Query 入门食用指南 |
| 2 | + |
| 3 | +对常见的请求场景进行一个封装,可以跨组件的管理请求状态,并且支持多个框架版本。 |
| 4 | + |
| 5 | +由于V5版本只有纯英文文档,因此在进行阅读后记录它的基本用法,哪天用到了可以快速使用。 |
| 6 | + |
| 7 | +本篇记录的是`react-query`的用法,其它版本大差不差。 |
| 8 | + |
| 9 | +### QueryClient && QueryClientProvider |
| 10 | + |
| 11 | +```tsx |
| 12 | +const queryClient = new QueryClient() |
| 13 | + |
| 14 | +ReactDOM.createRoot(document.getElementById('root')!).render( |
| 15 | + <React.StrictMode> |
| 16 | + <QueryClientProvider client={queryClient}> |
| 17 | + <App/> |
| 18 | + </QueryClientProvider> |
| 19 | + </React.StrictMode>, |
| 20 | +) |
| 21 | +``` |
| 22 | + |
| 23 | +实例化`QueryClient`类,并使用`QueryClientProvider`组件注入后,所有子组件就可以通过`useQueryClient`来进行跨组件操作。 |
| 24 | + |
| 25 | +### useQuery |
| 26 | + |
| 27 | +`query`一般用于`get`请求的场景,页面加载就可以发起请求。 |
| 28 | + |
| 29 | +```typescript |
| 30 | +const [enabled, setEnabled] = useState(false) |
| 31 | + |
| 32 | +const { isPending, isFetching, isLoading, data, refetch } = useQuery({ |
| 33 | + // 这里可以放不同的数据类型,一般可以将组件的state放进去 |
| 34 | + queryKey: ['useQuery'], |
| 35 | + // 这里会传入一些参数 |
| 36 | + queryFn: ({ queryKey }) => axios().then((response) => { |
| 37 | + // 这里需要自行处理response得到的数据,最后就会放到data中 |
| 38 | + return response |
| 39 | + }), |
| 40 | + enabled, |
| 41 | + placeholderData: keepPreviousData, |
| 42 | + initialData: initialData |
| 43 | +}) |
| 44 | +``` |
| 45 | + |
| 46 | +- `queryFn`为请求函数,无论是使用`fetch`还是`axios`只要是返回`Promise`都可以使用。在调用`queryFn`时,`queryKey`会作为参数上下文中的`queryKey`传给`queryFn`。 |
| 47 | +当需要将请求状态更改为`error`状态时可以在`Promise`内返回`reject`或`throw new Error`。 |
| 48 | + |
| 49 | +- `queryKey`可以用于在其它地方通过`queryClient`调用或通过其定义的`queryKey`在变更时重新发起请求,`queryKey`的数组内支持数字,字符串以及可序列化的对象。 |
| 50 | + |
| 51 | +- `useQuery`会返回请求的状态以及数据源,这里需要注意`isPending`和`isFetching`的区别,`isPending`和`devtools/network`的`pending`不同, |
| 52 | +它代表的是数据的获取状态,`isFetching`才是请求的状态。`isLoading`是`isPending && isFetching`的计算值,一般用于惰性发起请求。 |
| 53 | + |
| 54 | +#### 惰性请求 |
| 55 | + |
| 56 | +- 当需要惰性(也可以说是根据条件决定)发起请求时,可以定义`enabled`为组件的`state`,然后将`state`放入`queryKey`当中,也可以从`useQuery`内使用`refetch`来手动发起请求,不过官方并不推荐手动发起。 |
| 57 | + |
| 58 | +#### 分页场景 |
| 59 | + |
| 60 | +- 在分页场景下,可以定义`placeholderData`属性,可以使用自带的`keepPreviousData`或自定义返回值,这样在切换分页时,在新数据获取之前,可以保留旧的数据。 |
| 61 | +这个属性可以理解为分页场景下的`placeholder`。 |
| 62 | + |
| 63 | +#### 初始数据 |
| 64 | + |
| 65 | +- 如果想在发起请求之前放一些假数据或从其它地方得到的数据在那,那么可以定义`initialData`,数据类型要和`data`内的相同,支持直接定义或函数返回。 |
| 66 | + |
| 67 | +### useInfiniteQuery |
| 68 | + |
| 69 | +```typescript |
| 70 | +const { data, fetchNextPage, fetchPreviousPage, hasNextPage } = useInfiniteQuery({ |
| 71 | + queryKey: ['infinite'], |
| 72 | + queryFn: ({ pageParam }) => axios(pageParam).then((res) => res.data), |
| 73 | + initialPageParam: { |
| 74 | + page: 1, |
| 75 | + size: 10 |
| 76 | + }, |
| 77 | + getNextPageParam: (lastPage, allPage, pageParam) => { |
| 78 | + // 在此返回下一页的pageParam |
| 79 | + }, |
| 80 | + getPreviousPageParam: () => { |
| 81 | + // 用法与getNextPageParam一致,但不是必填项 |
| 82 | + } |
| 83 | +}) |
| 84 | +``` |
| 85 | + |
| 86 | +- `queryKey`与`queryFn`和`useQuery`的用法基本一致,不过`queryFn`中多了一个`pageParam`参数可供使用。 |
| 87 | + |
| 88 | +- 使用`useInfiniteQuery`时必须传入`initialPageParam`和`getNextPageParam`。`initialPageParam`为发起请求的初始值,一般为页码和页数或只有页码。 |
| 89 | +`getNextPageParam`用于获取下一页数据(如调用了`fetchNextPage`)时的`pageParam`,如果后端提供了`下一页`的字段,那么可以直接使用,若未提供则需要手动对页数进行`+1`。 |
| 90 | + |
| 91 | +- `hasNextPage`是`getNextPageParam`返回值,不为`undefined`或`null`时都为`true`。 |
| 92 | + |
| 93 | +- 对应`NextPage`,`useInfiniteQuery`也提供了`getPreviousPageParam`以及其它和`NextPage`相似的功能,不过需要注意的是,`getPreviousPageParam`不是获取上一页,而是从头部插入数据。 |
| 94 | + |
| 95 | +- 在`data`上和`useQuery`不同,`data`则变为一个对象,包含`page`和`pageParams`,前者是请求获取的数据源,后者则是每次`Next`或`Previous`所得到的`pageParam` |
| 96 | + |
| 97 | +- 需要注意的是这里的`page`是由每一次获取到的数据一起拼接而成,是一个二维或多维数组,在使用时需注意使用方式。 |
| 98 | + |
| 99 | +```tsx |
| 100 | +{ |
| 101 | + data?.pages.map((group, index) => ( |
| 102 | + <Fragment key={index}> |
| 103 | + {/*这里才是请求到的数据*/} |
| 104 | + {group.map((item) => <p key={item.value}>{item.label}</p>)} |
| 105 | + </Fragment> |
| 106 | + )) |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +### useMutation |
| 111 | + |
| 112 | +常用于`post`场景,需手动发起。 |
| 113 | + |
| 114 | +```typescript |
| 115 | +const { mutateAsync, mutate, isPending, isSuccess } = useMutation({ |
| 116 | + mutationKey: ['useMutation'], |
| 117 | + mutationFn: (params) => axios(params), |
| 118 | + onMutate: () => {}, |
| 119 | + onSuccess: () => {}, |
| 120 | + onError: () => {}, |
| 121 | + onSettled: () => {} |
| 122 | +}) |
| 123 | +``` |
| 124 | + |
| 125 | +- `mutationKey`与`mutationFn`和`query`中的用法基本一致,`mutationFn`的参数来自于`mutate`或`mutateAsync`传递的值。 |
| 126 | + |
| 127 | +- `useMutation`具有独特的回调事件,`onMutate`可以理解为发起请求前需要进行的操作,其余三个回调事件可以对应理解为`Promise`中的`then`,`catch`,`finally`。 |
| 128 | + |
| 129 | +- `mutate`与`mutateAsync`的使用方法一致,区别是若想触发`useMutation`上定义的回调之外的其他回调,`mutateAsync`可以向使用`Promise`那样注册`then/catch/finally`, |
| 130 | +而`mutate`需要作为第二个参数传入其回调事件。 |
| 131 | + |
| 132 | +```typescript |
| 133 | +mutate(params, { |
| 134 | + onSuccess: () => {}, |
| 135 | + onError: () => {}, |
| 136 | + onSettled: () => {} |
| 137 | +}) |
| 138 | +``` |
0 commit comments