共计 4805 个字符,预计需要花费 13 分钟才能阅读完成。
当 2018 年 GraphQL 特地是 Apolllo Client 开始风行之后,很多人开始认为它将代替 Redux,对于 Redux 是否曾经掉队的问题常常被问到。
我很清晰地记得我过后对这些观点的不了解。为什么一些数据申请的库会代替全局状态治理库呢?这两者有什么关联呢?
已经我认为像 Apollo 这样的 Graphql 客户端只能用来申请数据,就像 axios 一样,你依然须要一些形式来让申请的数据能够被应用程序拜访到。
我发现我大错特错。
客户端状态 vs 服务端状态
Apollo 提供的不仅仅是形容所需数据同时获取数据的能力,它同时提供了针对这些服务端数据的缓存能力。这意味着你能够在多个组件中应用雷同的 useQuery
hook,它只会触发一次数据申请并且依照申请的先后顺序返回缓存中的数据。
这看起来跟咱们(包含很多除了咱们以外的团队)在一些场景应用 redux
的目标很类似:从服务器获取数据,而后让这部分数据能够在所有中央能够被拜访到。
所以仿佛咱们常常将这些服务端数据当成客户端状态来对待,除了这些服务端数据(比方:一个文章列表,你须要显示的某个用户的详细信息,…),你的利用并不真正领有它。咱们只是借用了最新版本的一份数据而后展现给用户。服务端才真正领有这部分数据。
对于我来说,这给了我一个如何对待数据的新的思路。如果咱们能利用缓存来显示咱们不领有的那局部数据,那么剩下的利用须要解决的真正的客户端状态将大大减少。这使我了解了为什么很多人认为 Apollo 能够在很多场景代替 redux。
React Query
我始终没有机会应用 GraphQL。咱们有现成的 REST API,并没有遇到冗余申请的问题,目前齐全沟通。并没有足够的理由让咱们转换到 GraphQL,特地是你还须要让后端服务配合进行改变。
然而我还是艳羡 GraphQL 带来的前端数据申请(包含 loading 和谬误态的解决)的简洁。如果 React 生态中有类似的针对 REST API 的计划就好了。
让咱们来看看 React Query 吧。
由 Tanner Linsley 在 2019 年开发的 React Query 使得在 REST API 中也能够应用到 Apollo 所带来的益处。他反对任何返回 Promise 的函数并且应用了 stale-while-revalidate
缓存策略。库自身有一些默认行为能够尽可能保证数据的实时性,同时尽可能快的将数据返回给用户,让人们感觉近乎实时的体验以提供优良的用户体验。在这之上,他同时提供了灵便的自定义能力来满足各种场景。
这篇文章并不会对 React Query 进行具体的介绍。
我认为官网文档曾经对应用和概念进行了很好的介绍,同时也有很多对于这方面的视频,并且 Tanner 开了一门课程能够让你相熟这个库。
我将会更多的关注在官网文档之外的一些实际上的介绍,当你曾经应用这个库一段时间之后,兴许这些介绍对你会有所帮忙。这其中有一些我过来几个月在深度应用 React Query 以及从 React Query 社区中总结出的教训。
对于默认行为的解释
我置信 React Query 的默认行为是通过三思而行的,然而他们有时会让你措手不及,特地是刚开始应用的时候。
首先,React Query 并不会在每次 render 的时候都执行 queryFn
,即便默认的staleTime
是 0。你的利用在任何时候可能会因为各种起因从新 render,所以如果每次都 fetch 是疯狂的!
如果你看到了一个你不心愿的 refetch,这很可能是因为你刚聚焦了以后窗口同时 React Query 执行了 refetchOnWindowFocus
,这在生产环境是一个很棒的个性:如果用户在不同的浏览器 tab 之间切换,而后回到了你的利用,一个后盾的 refetch 会被主动触发,如果在同一个工夫服务端数据产生了变更,那屏幕上的数据会被更新。所有这些会在看不到 loading 态的状况下产生,如果数据和缓存中的数据比照没有变动的话,你的组件不会进行从新 render。
在开发阶段,这个景象会呈现得更加频繁,特地是当你在浏览器 DevTools 和你的利用之间切换的时候。
其次,cacheTime
和 staleTime
的区别仿佛常常让人感到困惑,所以让我来阐明一下:
- StaleTime:一个查问变成生效之前的时长。如果查问是无效的,那么查问就会始终应用缓存中的数据,不会进行网络申请。如果查问是处于生效状态(默认状况下查问会立刻生效),首先依然会从缓存中获取数据,然而同时后盾在满足肯定条件的状况下会发动一次查问申请。
- CacheTime:查问从变成非激活态到从缓存中移除继续的时长。默认是五分钟,当没有注册的观察者的时候,查问会变成非激活态,所以如果所有应用了某个查问的组件都销毁的时候,这个查问就变成了非激活态。
大多数状况下,如果你要扭转这两个设置其中的某一个的话,大部分状况下应该批改staleTime
。我很少会须要批改cacheTime
。在文档外面也有一个对于这个的解释。
应用 React Query DevTools
DevTools 会帮忙你更好的了解查问中的状态。它会通知你以后缓存中的数据是什么,所以你能够更不便的进行调试。除了这些,我发现在 DevTools 中能够模仿你的网络环境来更直观的看到后盾 refetch,因为本地服务个别都很快。
把 query key 了解成一个依赖列表
我这里所说的依赖列表是类比 useEffect
中说到的依赖列表,我假如你曾经对 useEffect
曾经比拟相熟了。
为什么这两者会是类似的呢?
因为 React Query 会触发 refetch 当 query key 发生变化。所以当咱们给 queryFn
传了一个变量的时候,大部分状况下咱们都是心愿当这个变量发生变化的时候能够申请数据。相比于通过简单的代码逻辑来手动触发一个 refetch,咱们能够利用 query key:
type State = 'all' | 'open' | 'done'
type Todo = {
id: number
state: State
}
type Todos = ReadonlyArray<Todo>
const fetchTodos = async (state: State): Promise<Todos> => {const response = await axios.get(`todos/${state}`)
return response.data
}
export const useTodosQuery = (state: State) =>
useQuery(['todos', state], () => fetchTodos(state))
这里,设想咱们的 UI 显示了一个带有过滤器的 todo 列表。咱们会有一些本地状态来存储过滤器的数据,当用户扭转了过滤条件之后,咱们会更新本地的状态,而后 React Query 会主动触发一个 refetch,因为 query key 产生了变动。咱们最终实现了过滤状态和查问函数的同步,这与 useEffect 中的依赖列表很类似。我素来没有没有呈现过给 queryFn
传了一个变量,然而这个变量不是 queryKey
的一部分的状况。
一个新的缓存入口
因为 query key 被用作缓存的 key,所以当你把状态从 all 改成 done 的时候,你会失去一个新的缓存入口,当你第一次切换过滤状态的时候,会导致一个强制的 loading 状态(很可能会限度一个 loading 动画)。这当然不是最现实的,所以你能够应用 keepPreviousData
来解决这种状况,或者你能够应用 initialData 来为新的缓存入口预填充数据。下面那个例子能够很完满的解释这个状况,因为咱们能够做一些客户端的数据预过滤:
type State = 'all' | 'open' | 'done'
type Todo = {
id: number
state: State
}
type Todos = ReadonlyArray<Todo>
const fetchTodos = async (state: State): Promise<Todos> => {const response = await axios.get(`todos/${state}`)
return response.data
}
export const useTodosQuery = (state: State) =>
useQuery(['todos', state], () => fetchTodos(state), {initialData: () => {const allTodos = queryClient.getQueryData<Todos>(['todos', 'all'])
const filteredData =
allTodos?.filter((todo) => todo.state === state) ?? []
return filteredData.length > 0 ? filteredData : undefined
},
})
当初,每次用户切换过滤条件的时候,如果咱们没有数据,咱们会尝试用 ’all todos’ 缓存中的数据来预填充。咱们能够马上就显示 ’done’ 的 todo 给用户,他们能够在后盾 fetch 完结之后看到更新之后的列表。留神 v3 版本中,你须要设置 initialStale
属性来触发一个后盾 fetch。
我认为这简略的几行代码能够给你带来很好的用户体验的晋升。
把服务端状态和客户端状态离开
这个观点和我上个月写的文档一样:如果你从 useQuery
中拿到了数据,不要把这部分数据放到本地状态中。次要的起因是这样会使得 React Query 所有后盾更新生效,因为复制进去的本地状态不会自动更新。
如果你心愿获取一些默认数据来设置一个表单的默认值,而后应用数据来渲染表单,那是能够的。后盾更新并不会因为表单曾经初始化就疏忽之后更新的数据。所以如果你想打到这个目标,确保通过设置 staleTime
来防止触发不必要的后盾 refetch:
const App = () => {const { data} = useQuery('key', queryFn, { staleTime: Infinity})
return data ? <MyForm initialData={data} /> : null
}
const MyForm = ({initialData} ) => {const [data, setData] = React.useState(initialData)
...
}
enabled 属性是很弱小的
useQuery
hook 有很多属性能够用来自定义他的行为,enabled
属性是很弱小的一个,它能够让你做很多有意思的事件。上面是一些咱们能够利用它来实现的性能:
- 依赖查问
在一个查问中获取数据,而后第二个查问只有当咱们胜利的从上一个查问中获取数据的时候才会触发
- 开启 / 敞开查问
假如咱们有一个定时查问,通过 refetchInterval
来实现,然而当一个弹窗关上的时候咱们能够暂停这个查问,防止弹窗前面的内容产生变更。
- 期待用户输出
比方咱们有一些过滤条件作为 query key,然而当用户还没进行过滤操作的时候能够不进行查问。
不要把 queryCache 当成本地状态管理器
如果你要批改 queryCache,它应该只产生在乐观更新或者在变更之后拿到后盾返回的新数据的时候。记住任何一个后盾 refetch 都会笼罩这些数据,所以能够应用其余本地状态治理库
创立自定义 hook
即便你只是封装一个 useQuery
调用,创立一个自定义 hook 通常状况下也是值得的,因为:
- 你能够把实在的数据获取逻辑和 UI 拆散,过后把它和 useQuery 调用封装在一起
- 你能够把对于某个 query key 的应用都放在同一个文件外面
- 如果你须要批改一些设置或者减少一些数据转换逻辑,你能够在一个中央进行
在下面的 todo 例子外面曾经有一些应用场景。
我心愿这些实践经验能够帮忙你相熟 React Query,去试试吧。
原文地址:https://tkdodo.eu/blog/practi…