美文网首页React Query
React Query 学习 5 最佳实践

React Query 学习 5 最佳实践

作者: 吴摩西 | 来源:发表于2022-12-11 12:02 被阅读0次

    https://tkdodo.eu/blog/react-query-data-transformations

    数据变化

    服务器端给前端的数据不能总 (或者总不能) 满足前端的需求,需要对其进行一定程度的转换。

    0. 在服务器端处理

    • 好处:服务器端的数据总能在 devtool 上清晰看到
    • 一般:不在前端,需要单独的维护
    • 不好:不总是可能

    1. 响应函数处理

    const fetchTodos = async (): Promise<Todos> => {
      const response = await axios.get('todos')
      const data: Todos = response.data
    
      return data.map((todo) => todo.name.toUpperCase())
    }
    
    export const useTodosQuery = () => useQuery(['todos'], fetchTodos)
    
    • 好处: 离着服务端代码很近
    • 一般: 不能复用缓存
    • 不好: 每次都需要处理
    • 不好: 如果前端有单独的 api 层,不能随意修改

    2. 在 Render 中修改

    • 好处: 通过 useMemo 优化,实际上,与响应函数处理没啥区别,也是每次都要处理
    • 一般: 数据结构不能在 devtool 里面清晰看到
    • 不好: 代码不清晰
    • 不好: 数据可能是空的,或者错误,或者未准备好的。
      实际上,在 useMemo 中还能读去其他组件状态,能更方便的处理数据。

    3. 通过 useQuery 的 select 处理

    const transformTodoNames = (data: Todos) =>
      data.map((todo) => todo.name.toUpperCase())
    
    export const useTodosQuery = () =>
      useQuery(['todos'], fetchTodos, {
        // ✅ uses a stable function reference
        select: transformTodoNames,
      })
    
    • 好处: 最佳实践
    • 好处: 支持部分的监听
    • 一般: 每个监听者对于数据的处理可以有所不同
    • 一般: 结构化共享被执行了两次

    React Query 渲染优化

    无必要的渲染,是大家尽量避免的,然而,更要避免的事需要渲染而没有渲染。在优化之前,需要阅读一下的一些 topic

    使用 notifyOnChangeProps 控制响应的变化

    export const useTodosQuery = (select, notifyOnChangeProps) =>
      useQuery(['todos'], fetchTodos, { select, notifyOnChangeProps })
    export const useTodosCount = () =>
      useTodosQuery((data) => data.length, ['data'])
    

    可以使用 notifyOnChangeProps='tracked',自动跟踪用到的变量。

    结构分享

    重新查询时,会尽可能的重用状态。例如 todo 如果是一个列表,而下面的代码尝试监听第二个元素,如果列表重取。而第二条没有变化,下面的状态就不会更新。

    // ✅ will only re-render if _something_ within todo with id:2 changes
    // thanks to structural sharing
    const { data } = useTodo(2)
    

    有效的查询 key

    值得再次强调,query key 应该与用它的组件放在一起。queries可以复合一些 hooks。让 query key 只包含在局部作用域。

    - src
      - features
        - Profile
          - index.tsx
          - queries.ts
        - Todos
          - index.tsx
          - queries.ts
    

    使用 query function 上下文

    babel-plugin-react-query-key-gen 可以检查 query key 不全的情况如下:

    export const useTodos = () => {
      const { state, sorting } = useTodoParams()
    
      // 🚨 can you spot the mistake ⬇️
      return useQuery(['todos', state], () => fetchTodos(state, sorting))
    }
    

    然而,可以直接使用 queryKey 参数

    const fetchTodos = async ({ queryKey }) => {
      // 🚀 we can get all params from the queryKey
      const [, state, sorting] = queryKey
      const response = await axios.get(`todos/${state}?sorting=${sorting}`)
      return response.data
    }
    
    export const useTodos = () => {
      const { state, sorting } = useTodoParams()
    
      // ✅ no need to pass parameters manually
      return useQuery(['todos', state, sorting], fetchTodos)
    }
    

    使用 更严格的类型约束

    const todoKeys = {
      all: ['todos'] as const,
      lists: () => [...todoKeys.all, 'list'] as const,
      list: (state: State, sorting: Sorting) =>
        [...todoKeys.lists(), state, sorting] as const,
    }
    
    const fetchTodos = async ({
      queryKey,
    }: // 🤯 only accept keys that come from the factory
    QueryFunctionContext<ReturnType<typeof todoKeys['list']>>) => {
      const [, , state, sorting] = queryKey
      const response = await axios.get(`todos/${state}?sorting=${sorting}`)
      return response.data
    }
    
    export const useTodos = () => {
      const { state, sorting } = useTodoParams()
    
      // ✅ build the key via the factory
      return useQuery(todoKeys.list(state, sorting), fetchTodos)
    }
    

    react query 是一个状态管理库

    定制 staleTime

    太小的 staleTime 会导致大量的数据重取。一般建议在 20s 以上,这个参数默认是 0。

    React Router 也可以加载数据

    react router 6.4.0 开始支持在 route 上挂载 loader / action

    createBrowserRouter([
      {
        path: "/teams/:teamId",
        loader: ({ params }) => {
          return fakeGetTeam(params.teamId);
        },
      },
    ]);
    

    可以跟 react query 混用

    // ⬇️ define your query
    const contactDetailQuery = (id) => ({
      queryKey: ['contacts', 'detail', id],
      queryFn: async () => getContact(id),
    })
    
    // ⬇️ needs access to queryClient
    export const loader =
      (queryClient) =>
      async ({ params }) => {
        const query = contactDetailQuery(params.contactId)
        // ⬇️ return data or fetch it
        return (
          queryClient.getQueryData(query.queryKey) ??
          (await queryClient.fetchQuery(query))
        )
      }
    
    export default function Contact() {
      const params = useParams()
      // ⬇️ useQuery as per usual
      const { data: contact } = useQuery(contactDetailQuery(params.contactId))
      // render some jsx
    }
    

    预先查询缓存

    • 如果支持服务器端渲染,在服务器端进行预先获取数据。
    • 如果能在 route 中支持 loader,在route 中预先加载数据。
    image.png

    可以通过两种方式来预先缓存,一种是拉的方式,即通过 initialData 来指定数据。另一种是推的方式。例如在 list 获取完后,直接给每个 id 设置对应的缓存。

    const useTodos = () => {
      const queryClient = useQueryClient()
      return useQuery({
        queryKey: ['todos', 'list'],
        queryFn: async () => {
          const todos = await fetchTodos()
          todos.forEach((todo) => {
            // ⬇️ create a detail cache for each item
            queryClient.setQueryData(['todos', 'detail', todo.id], todo)
          })
          return todos
        },
      })
    }
    
    • 好处: query 可以明确的知道对应数据的挂载时间,从而考量数据的新鲜度。
    • 一般:回调无法正常执行,例如 onSuccess, onQuery 的调用,可能会导致相同的数据会写入缓存多次。
    • 一般:可能会在缓存中写入无用的数据。
    • 不好:推的数据可能会被垃圾回收。导致缓存失效。

    相关文章

      网友评论

        本文标题:React Query 学习 5 最佳实践

        本文链接:https://www.haomeiwen.com/subject/gpfofdtx.html