likes
comments
collection
share

react-query手把手教程②-深入查询键及查询函数

作者站长头像
站长
· 阅读数 15

目录

版本说明

本教程基于react-query@4版本编写,此版本目前(2022-06-08)为alpha版本。

在线演示基于stackblitz平台

查询键常见问题

查询键的值不能重复,需要保持唯一

想象一下这样的场景,在localStorage中设置了下面的数据,目的是以userID为键,把当前的用户数据保存到缓存内

const userId = 1;
localStorage.setItem(userId, {username: 'fed-orange'})

接着再把某商品的内容以ProductId为键,把当商品的数据保存到缓存内

const productId = 1;
localStorage.setItem(productId, {name: 'orange'})

此时,会发现你保存到localStorage仅仅只保存了你后面缓存的值。

同理,在react-query中,如果这么做同样会遇到这样的问题 请求用户数据

const usersQuery = useQuery(
  [userId],
  fetchUsers
);

请求商品数据

const productsQuery = useQuery(
  [productId],
  fetchProducts
);

如果userIdproductId相同时,后面请求的数据会覆盖前面请求的数据!解决这个问题的办法是:可以在数组的第一个元素中,放一个字符串来标识当前的数据类型(可以参照笔者之前提到的设计查询键的小建议中的内容),就可以解决这个问题。

const usersQuery = useQuery(
  ['users', userId],
  fetchUsers
);

const productsQuery = useQuery(
  ['products', productId],
  fetchProducts
);
这样做不仅能更好的写查询键,在你调试数据的过程里,在DevTools中你能很方便的区分出来不同的数据(不要自己给自己挖坑,在DevTools是以查询键为维度列出所有缓存的数据)!

查询函数

查询函数使用小妙招

相信在之前的介绍里面,可能把你的思维禁锢在查询函数只能使用fetch(或者axios之类的库)来调用http接口。 但是实际上,所有的Promise函数,都可以作为查询函数。举个例子,浏览器中异步的API接口——获取当前定位的API,可以封装为一个查询函数。

  const getLocation = async () =>
    new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(resolve, reject);
    });

接着在useQuery中使用上面的查询函数

在stackblitz查看例子

例子1👇🏻

import * as React from 'react';
import { useQuery } from 'react-query';

export default function App() {
  const getLocation = async () =>
    new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(resolve, reject);
    });

  const locationQuery = useQuery(['location'], getLocation);
  return (
    <div>
      <h1>获取地理位置</h1>
      {locationQuery.isLoading ? (
        '地理位置获取中'
      ) : locationQuery.isError ? (
        locationQuery?.error?.message
      ) : (
        <p>
          你的地理位置是:{locationQuery.data.coords.latitude},
          {locationQuery.data.coords.longitude}
        </p>
      )}
    </div>
  );
}

自定义hook封装react-query的查询请求

前面举的例子中,很多的请求都非常简单,并且仅仅在一个地方使用,因此我们无需对代码进行封装。但是假如请求本身涉及到非常复杂的处理,并且可能在多个组件重复使用的时候,我们又回到了最初的原点,发现很多重复代码需要复制粘贴!但是一旦你有这个念头的时候,就是万恶的开始,一旦你这么做了,后期的维护成本将大大增加,修改一个地方,你不得不把类似的代码都更改一遍,费时费力!

下面我们把之前的请求仓库issue的例子抽离成自定义hook,这个是Github中针对该api的相关说明文档,有兴趣的同学可以自行查阅:issues api文档,如果想扩展相关功能欢迎在stackblitz里fork后自行尝试。

我们再来看一下之前的查询函数

  const fetchData = ({ queryKey }) => {
    const [, owner, repo] = queryKey;

    return fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
      headers: {
        Authorization: '',
      },
    }).then(async (response) => {
      // 如果请求返回status不为200 则抛出后端错误
      if (response.status !== 200) {
        const { message } = await response.json();

        throw new Error(message);
      }

      return response.json();
    });
  };

接下来我们定义一个名为useGithubIssuesQuery的钩子

import { useQuery } from 'react-query';

const useGithubIssuesQuery = ({ owner, repo }) => {
  const fetchData = ({ queryKey }) => {
    const [, owner, repo] = queryKey;

    return fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
      headers: {
        Authorization: '',
      },
    }).then(async (response) => {
      // 如果请求返回status不为200 则抛出后端错误
      if (response.status !== 200) {
        const { message } = await response.json();

        throw new Error(message);
      }

      return response.json();
    });
  };

  return useQuery(['issues', owner, repo], fetchData);
};

export default useGithubIssuesQuery;

此时在react组件获取数据时,要做的仅仅是使用useGithubIssuesQuery钩子,传入owner及repo参数即可,其它的一切都不需要关心,如果在别的组件中需要请求仓库issue相关的接口,只要使用useGithubIssuesQuery钩子即可。并且在后期添加功能时,只需要找到对应的自定义钩子并做相关改动即可,大大提高了开发效率。

在多个组件中引用相同key值的数据,react-query不会进行多次请求,可以放心使用!

总结

查询键

  • 请保证查询键的唯一性。如果有重复的查询键,将会导致数据覆盖问题
  • 查询键的设计可以遵循从通用到特定的规则,第一个元素可以是字符串来描述数据类型
  • 查询键的元素可以是字符串、数字、嵌套数组、对象
  • 在查询键变化时(包括对象内的变化,react-query将会进行深度比较),react-query将会重新调用查询函数

查询函数

  • 查询函数可以是任何Promise函数,包括但不局限于fetch、axios、graphql请求、异步浏览器api等

其它

建议针对需要复杂处理且在多处使用的请求,封装自定义钩子来提高开发效率。