这篇文章主要介绍“ReactQuery渲染优化的方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“ReactQuery渲染优化的方法是什么”文章能帮助大家解决问题。
isFetching
在之前的例子中我说过,下面这个组件只会在todos的length变化时才会重新渲染,其实我只说了一部分事实:
export const useTodosQuery = (select) => useQuery(['todos'], fetchTodos, { select })export const useTodosCount = () => useTodosQuery((data) => data.length)function TodosCount() { const todosCount = useTodosCount() return <div>{todosCount.data}</div>}
每次发生后台refetch的时候,这个组件都会下面的数据分别进行一次渲染:
{ status: 'success', data: 2, isFetching: true }{ status: 'success', data: 2, isFetching: false }
这是因为React Query在每个查询中返回了很多基本信息,isFetching
就是其中一个。这个属性在请求正在发生的时候会被设置为true。这个在你想要展示一个后台请求的loading标志的时候特别有用。但是如果你不需要,那确实会造成一些不必要的渲染。
notifiOnChange
对于上面说到的这个场景,React Query提供了notifyOnChangeProps
参数。他可以在每个场景单独设置来告诉React Query:只在这些属性发生变化的时候再通知我。通过将这个参数设置为['data']
,我们可以实现一个新的版本:
export const useTodosQuery = (select, notifyOnChangeProps) => useQuery(['todos'], fetchTodos, { select, notifyOnChangeProps })export const useTodosCount = () => useTodosQuery((data) => data.length, ['data'])
保持同步
尽管上面的代码可以正常工作,但是它很容易就会造成不同步。如果我们希望针对error
进行特殊处理呢?又或者我们需要使用isLoading
属性呢?我们不得不确保notifyOnChangeProps
属性和我们实际用到的数据保持同步。如果我们忘记将某个数据添加到属性里面,而只监听data属性的变化,当查询返回错误,同时我们也要展示这些错误的时候,我们的组件并不会重新渲染。这个问题当我们把这些属性写死在自定义hook的时候格外明显,因为我们并不知道使用自定义hook的组件实际上会用到哪些数据:
export const useTodosCount = () => useTodosQuery((data) => data.length, ['data'])function TodosCount() { // ???? we are using error, but we are not getting notified if error changes! const { error, data } = useTodosCount() return ( <div> {error ? error : null} {data ? data : null} </div> )}
就像我在文章开头免责声明中说的,我认为这是比偶尔发生的不必要的重新渲染更坏的事情。当然,我们可以传参数给自定义hook,但是这还是需要手动处理,是否有什么方式可以自动处理这个情况呢?请看:
被追踪的查询
这是我感受特别自豪的一个特性,这也是我对这个库第一个重大的贡献。如果你将notifyOnChangeProps
设置为'tracked'
,React Query会跟踪你在渲染过程中用到的数据,会自动计算依赖列表。最终的效果就跟你手动维护这个列表一样,除了你不用再去关注这个问题以外。你也可以全局开启这个特性:
const queryClient = new QueryClient({ defaultOptions: { queries: { notifyOnChangeProps: 'tracked', }, },})function App() { return ( <QueryClientProvider client={queryClient}> <Example /> </QueryClientProvider> )}
利用这个特性,你再也不用考虑重新渲染。当然这个特性也有一些限制,这就是为什么这个特性是一个可选项:
如果你使用对象剩余属性结构的语法的话,最终所有属性都会被追踪。正常的解构语法是没问题的,不要这么做:
// ???? will track all fieldsconst { isLoading, ...queryInfo } = useQuery(...)// ✅ this is totally fineconst { isLoading, data } = useQuery(...)
被追踪的查询只会追踪render过程中用到的数据。如果你只在effects中用到了这些数据,他们并不会被追踪。
const queryInfo = useQuery(...)// ???? will not corectly track dataReact.useEffect(() => { console.log(queryInfo.data)})// ✅ fine because the dependency array is accessed during renderReact.useEffect(() => { console.log(queryInfo.data)}, [queryInfo.data])
被追踪的查询不会在每次render的时候被重置,所以只要你使用了一次某个数据,你就会在整个组件的生命周期内追踪这个数据:
const queryInfo = useQuery(...)if (someCondition()) { // ???? we will track the data field if someCondition was true in any previous render cycle return <div>{queryInfo.data}</div>}
结构化共享
一个不同的但是并没那么重要的React Query默认开启的渲染优化是结构化共享。这个特性确保数据在所有地方是引用唯一的。举个例子,假设我们有下面这个数据结构:
[{ "id": 1, "name": "Learn React", "status": "active" },{ "id": 2, "name": "Learn React Query", "status": "todo" }]
现在假设我们将第一个todo转为done,然后进行了一次后台refetch。我们会从后端拿到一个全新的json:
{ "id": 1, "name": "Learn React", "status": "active" },{ "id": 1, "name": "Learn React", "status": "done" },{ "id": 2, "name": "Learn React Query", "status": "todo" }]
现在React Query会尝试对比新老状态,尽可能多的复用老的状态。在上面的例子中,todo数据会是一个新的对象,因为我们更新了一个todo。第一个id为1的对象也会是新的对象,但是对于id为2的对象我们会保持跟对应的旧数据一样的引用-React Query会将他复制一份同样的引用到新的数据,因为这部分数据并没有发生变化。
这使得使用selector进行部分订阅变得特别友好:
// ✅ will only re-render if something within todo with id:2 changes// thanks to structural sharingconst { data } = useTodo(2)
就像我之前提到的,对于selector来说结构化共享会用到两次:一次是在queryFn返回的结果上,另一次是在selector返回的结果上。在一些场景,特别是数据量比较大的场景,结构化共享会成为一个瓶颈。同时它只能使用在JSON可序列化的数据上。如果你不需要这个优化,你可以通过将`structuralSharing`设为false来关闭这个特性。
关于“ReactQuery渲染优化的方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。