React Query

React Query 学习 5 最佳实践

2022-12-11  本文已影响0人  吴摩西

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

数据变化

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

0. 在服务器端处理

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)

2. 在 Render 中修改

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
}

预先查询缓存

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
    },
  })
}
上一篇下一篇

猜你喜欢

热点阅读