当前位置: 首页 > 工具软件 > react-query > 使用案例 >

Api请求库react-query之缓存出神入化——作为全局状态管理

晁聪
2023-12-01

前言

在 react-query 入门文章:

《 React Api 请求最佳实践 react-query 3 使用教程( 比 swr 更好用更强大 )》

中,我们学会了 react-query 的基本使用。

我们注意到两个点:

  1. react-query 有全局的实例,可以管理全局的请求和数据,缓存就是在全局管理的,不论在哪个组件,存在 任意性

  2. 缓存是可以自由设定过期时间,当新鲜度 staleTimeInfinity 永不过期时,每次获取数据无需等待,是瞬间的,那么加上无限时间的缓存 cacheTime ,数据将永久存在于内存中,且获取是瞬间同步的,具有 持久性

好家伙,这不就是全局状态管理吗。

全局状态管理

全局 state

先使用 useQuery 造一个空请求,无限的过期时间和永远保持新鲜,让数据存在于全局:

import { useQuery } from 'react-query'

// 通过这个 key ,外界也可以用全局实例去做一些操作
export const KEY = '__key'

// 初始数据
export const initialData = {
  key1: 'value1',
  key2: 'value2'
}

// 暂且给我们的全局状态管理 hooks 起名为 useData 
export const useData = () => {

  const { data } = useQuery(KEY, {
    initialData,
    cacheTime: Infinity,
    staleTime: Infinity,
    refetchOnWindowFocus: false
  })

  return {
    data
  }
}

注意,使用 initialData 选项将自动将该初始值推入缓存,会使 useQuery 第一次就具有缓存,而该缓存将在全局永不过期。

改变 state 的方法

第二步,造一个设定值的函数 setData

import { useCallback } from 'react'
import { useQuery, useQueryClient } from 'react-query'

export const useData = () => {

  // 拿到全局实例
  const queryClient = useQueryClient()

  // ......
  
  const setData = useCallback(
    (newData) => {
      return queryClient.setQueryData(KEY, (pre) => ({
        ...pre,
        ...newData,
      }))
    },
    [queryClient]
  )

  return {
    data,
    setData
  }
}

这个就很简单了,届时我们通过:

	setData({ key2: 'new value2' })

就可以直接改变 key2 的值了,无需理会 key1

等等,这不是 context 变了个样子吗?毕竟全局实例 queryClient 是通过全局 context 透传的。我们用全局状态管理 redux 还要在意的就是能否执行异步任务!

全局异步任务

提到异步任务,那就到了一个 api 请求库的强项了,为异步而生的 useMutation

import { useCallback } from 'react'
import { useQuery, useQueryClient, useMutation } from 'react-query'

// 模拟一个 1.5s 的异步任务
const sleep = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        data: 'result',
      })
    }, 1500)
  })
}

export const useData = () => {

  // ......
  
  // 这是一个异步任务
  const asyncApi = useMutation(
    // 第一个参数为异步函数
    async () => {
      const res = await sleep()
      return res.data
    },
    // 第二个参数是一个配置对象,在其中处理生命周期
    {
      // ↓ 请求前的处理
      onMutate: (variables) => {
        // ↑ variables 为执行该异步任务传入的参数
		
		// ...

       // ↓ 返回值将挂载到整个生命周期的 context 上下文中
        return { key: 'value' }
      },
      
      // ↓ 请求成功的处理
      onSuccess: (data, variables, context) => {
        // ↓ data 为异步函数的返回结果,即上文的 res.data 也就是 'result'
        const newData = setData({
          key2: data,
        })
        
        // ↓ variables 为执行该异步任务传入的参数
        console.log('variables: ', variables)

		// ↓ context 为贯穿于生命周期的上下文对象,我们在请求前的生命周期 onMutate 中给其挂载了 { key: 'value' }
        console.log('context: ', context.key)
      },

      // ↓ 发生错误时进入的生命周期
	  onError: (err, variables, context) => {},

      // ↓ 无论结果如何最后都会进入的生命周期
      onSettled: (data, error, variables, context) => {}
    }
  )

  return {
    data,
    setData,
    asyncApi
  }
}

如此一来,异步任务也得到解决,通过 asyncApi.mutate 同步方法或 asyncApi.mutateAsync 异步方法均可调用该函数。

关于 useMutation 的 options 选项更多内容详见:useMutation

发现更多

其实到这里你会发现,useMutation 的可操作性很广,正如我们在之前一篇文章中所说的,useMutation 是一个主张性很强的 hooks ,他将我们的异步任务带入 react-query 的管辖,从而在生命周期产生各种副作用和交互的可能。

  • 你甚至可以给 useMutation 配置唯一的 key 以便在全局任何地方操作他;

  • 无论在 onSuccess 还是 asyncApi.data 操作返回值,都由你来决定;

  • 你可以通过全局实例的 setMutationDefaults 方法动态调整该 mutation 的 options,让生命周期都是动态的;

  • 从全局实例的 getMutationCache 方法获取每次请求的缓存得到无限利用可能;

  • 你可以在 onMutate 有预见性的更新数据,当 onError 存在错误时回滚,达到 事务管理

  • 异步请求自带 asyncApi.isLoading 请求 loading 态,自带请求失败重试 3 次(可配置)

等等无限自定义可能…

总结

谈了这么多,react-query 确实功能很丰富,但是他的全局状态管理终究是 context 为基础,既然是 context 就要避免不必要的渲染,context 需要考虑的思维在此处同理。

如果你使用 react-query 去做全局状态管理,请经常思考:

  1. 哪些组件需要 memo 优化?

  2. 哪些组件需要做抽离调整?(从 children 传入不必渲染的组件以达到 props.children 不变和非当前 renderLanes 渲染线条件避免渲染)

另外,阿里 hox 也是一个很不错的全局状态管理 + 逻辑的工具,只是其 model 将导致你写很多模板代码,并且没有 react-query 在异步处理上的灵活,但你无需产生 context 的焦虑。

 类似资料: