react-query手把手教程③-并行请求及依赖请求

并行请求及依赖请求

写在前面

由于国内较少有比较系统的react-query教程,因此笔者结合官方文档以及官方课程的内容,希望写一个较为全面的教程。本文将以各种例子作为切入点,尽可能通俗易懂地讲解相关知识点。如果有错误,还请大家在评论区指出,笔者会尽快改正。

目录

并行请求

什么是并行请求?

在日常开发中,前端请求后端接口时,通常会请求多个接口。比如下面的react-query代码:

   const usersQuery = useQuery(['users'], fetchUsers)
   const teamsQuery = useQuery(['teams'], fetchTeams)
   const projectsQuery = useQuery(['projects'], fetchProjects)

上面的示例中,请求了用户列表,团队列表,项目列表,此时的接口请求就是并行请求,每个请求互不干扰(你同样可以在数据库查询中,看到相关的概念,Google一下Parallel Queries)。谁先请求到数据,就显示哪一项数据。

假设现在有一个需求:分别展示github用户的仓库和代码片段列表 此时我们需要请求两个接口

获取用户仓库列表:

https://api.github.com/users/{username}/repos

获取用户代码片段列表:

https://api.github.com/users/{username}/gists

对这两个接口分别同时进行请求,哪个接口先返回了数据,就显示哪个接口的数据:

点我查看在线演示

import { useQuery } from "react-query";

const ReposAndGists = ({ username }) => {
  const reposQuery = useQuery(
    ["repos", username], 
    () => {
      return fetch(
        `https://api.github.com/users/${username}/repos`
      )
      .then((res) => res.json())
    }
  );

  const gistQuery = useQuery(
    ["gists", username], 
    () => {
      return fetch(
        `https://api.github.com/users/${username}/gists`
      )
      .then((res) => res.json())
    }
  );

  return (
    
     

仓库列表

    {reposQuery.isLoading &&

加载仓库列表中...

}     {reposQuery.isError && (        

加载仓库列表发生错误: {reposQuery.error.message}

    )}     {reposQuery.data && (        
            {reposQuery.data.map((repo) => (            
  • {repo.name}
  •         ))}        
    )}      

代码片段列表

    {gistQuery.isLoading &&

加载代码片段列表中...

}     {gistQuery.isError && (        

加载代码片段列表错误: {gistQuery.error.message}

    )}     {gistQuery.data && (        
            {gistQuery.data.map((gist) => (            
  • {gist.description}
  •         ))}        
    )}    
); };

另外一种情况是,希望对两个接口进行合并请求,等到两个接口都返回数据时,再向用户展示内容

在查询函数中使用Promise.all合并某两个请求

假设现在有一个需求:同时展示github用户的仓库和代码片段列表。此时后端并没有新的接口,你需要复用原有的老接口:

获取用户仓库列表:

https://api.github.com/users/{username}/repos

获取用户代码片段列表:

https://api.github.com/users/{username}/gists

需求的要求是等到所有数据返回后,才给用户显示内容。此时就需要使用Promise.all来合并请求。(ps:是否想起了被手写Pormise.all支配的恐惧)

下面是示例代码,同时使用github的username作为参数,请求该用户的仓库列表和代码片段列表

点我查看在线演示

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

const getReposAndGists = (username) => {
  return Promise.all([
    fetch(`https://api.github.com/users/${username}/repos`).then((res) =>
      res.json()
    ),
    fetch(`https://api.github.com/users/${username}/gists`).then((res) =>
      res.json()
    ),
  ]);
};

const ReposAndGists = ({ username }) => {
  const reposAndGistsQuery = useQuery(['reposAndGists', username], () =>
    getReposAndGists(username)
  );

  if (reposAndGistsQuery.isLoading) {
    return 

加载数据中...

; }  if (reposAndGistsQuery.isError) {    return

数据加载错误: {reposAndGistsQuery.error.message}

; }  if (!reposAndGistsQuery.data) {    return null; }  const [repos, gists] = reposAndGistsQuery.data;  return (    
     

仓库列表

     
          {repos.map((repo) => (          
  • {repo.name}
  •       ))}      
     
     

代码片段列表

     
          {gists.map((gist) => (          
  • {gist.description || '暂无描述'}
  •       ))}      
   
); }; export default ReposAndGists;

使用useQueries进行请求

如果需要动态生成请求,你会发现,在现有的hooks规则下使用useQuery无法实现这个需求。

因此react-query提供了useQueries来解决这个问题,下面的代码中,我们可以随意修改数组内容,此时react-query就会生成不同的请求:

下面的例子中,首先批量请求获取了数组内以下用户的所有仓库['facebook', 'vuejs', 'nestjs', 'mongdb'],在点击按钮后,重新批量获取了以下用户的仓库['microsoft', 'tesla'],做到了非常方便的动态请求。

点我查看在线演示

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

export default function App() {
  const [users, setUsers] = React.useState([
    'facebook',
    'vuejs',
    'nestjs',
    'mongodb',
  ]);

  const getRepos = (username) =>
    fetch(`https://api.github.com/users/${username}/repos`).then((res) =>
      res.json()
    );

  const userQueries = useQueries({
    queries: users.map((user) => {
      return {
        queryKey: ['user', user],
        queryFn: () => getRepos(user),
      };
    }),
  });
  return (
    
     

查看github用户的仓库

          {userQueries.map((query) =>        query.isLoading ? (          
加载中....
      ) : (          
              {query.data.map((item) => (              
  1. {item.full_name}
  2.           ))}          
      )     )}    
); }

依赖请求

什么是依赖请求?

假设现在有一个需求:给用户展示最新的一条的内容详情。但是现实情况是后端只提供了按时间倒序的列表接口和详情接口,并没有时间给你出新的接口。

此时可以想到的办法是:获取列表接口的第一条数据id之后再请求详情页接口,将获取的详情内容展示给用户。

这种情况就是依赖请求,请求B接口的某个参数依赖A接口请求返回的内容。

下面还是使用github的接口,给大家演示一个需求:获取react仓库标签列表中的第二个标签,请求这个标签的issue列表。

点我查看在线演示

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

const IssueLabelFilter = ({ owner, repo }) => {
  const labelsQuery = useQuery(['repos', owner, repo, 'labels'], () =>
    fetch(`https://api.github.com/repos/${owner}/${repo}/labels`).then((res) =>
      res.json()
    )
  );

  const labels = labelsQuery.data;

  const issuesQuery = useQuery(
    ['repos', owner, repo, 'issues'],
    () =>
      fetch(
        `https://api.github.com/repos/${owner}/${repo}/issues?labels=${labels[1].name}`
      ).then((res) => res.json()),
    {
      /**
       * 该配置项在value为true时,会触发接口请求。
       * 因此当第一个接口请求返回后,此时该配置项表达式为true
       * 会触发请求github中的issue列表 
       * ①
       */
      enabled: !!labels, 
    }
  );

  return (
    

标签

{labelsQuery.isLoading ? (

加载标签中...

) : (
    {labelsQuery.data.map((label) => (
  • {label.name}
  • ))}
)}

Issues {Array.isArray(issuesQuery.data) ? `(${labels[1].name})` : ''}

{issuesQuery.isLoading ? (

加载issues中...

) : (
    {issuesQuery.data.map((issue) => (
  • {issue.title}
  • ))}
)}
); }; export default function App() { return (
); }

在react-query中,当该组件被加载时,组件内的useQuery就会开始请求。

此时明显不符合需求,需求的要求是在加载完标签列表后,获取到第二个标签,再开始请求issues列表。

因此就需要使用到useQueryenabled参数,当参数值为false时,将会禁止请求接口。

现在回到上面的例子中,当labelsQuery请求还没有结果时,labels变量值为undefined,此时在①行代码中的值为false,当labelsQuery请求结束时,labels变量值为数组,此时在①行代码中的值为trueissuesQuery开始请求数据。完全符合需求的要求。

改进依赖查询接口一直为loading的问题

上面的例子中,issuesQuery在加载后,由于处于禁用状态(配置项enabled: false),此时isLoading将会一直处于true的状态,直到issuesQuery请求完成数据后变为false

这种提示会非常奇怪,明明有一段时间里issuesQuery没有真正的请求数据,为啥要一直显示加载标签中...的内容?

解决办法是:需要一个字段来区分查询函数当前并没有请求数据,处于摸鱼状态。

useQuery中当fetchStatus字段在为idle时,表示当前查询函数不在运行,处于摸鱼状态^ ^

fetchStatus一共有三个状态,fetching状态表示当前查询函数正在运行,idle状态表示当时查询函数不在运行。paused状态表示查询函数尝试运行,但是无法进行请求,最可能的原因是由于当前没有联网,处于离线状态。
点我查看在线演示
import * as React from 'react';
import { useQuery } from 'react-query';
import './style.css';

const IssueLabelFilter = ({ owner, repo }) => {
  const labelsQuery = useQuery(['repos', owner, repo, 'labels'], () =>
    fetch(`https://api.github.com/repos/${owner}/${repo}/labels`).then((res) =>
      res.json()
    )
  );

  const labels = labelsQuery.data;

  const issuesQuery = useQuery(
    ['repos', owner, repo, 'issues'],
    () =>
      fetch(
        `https://api.github.com/repos/${owner}/${repo}/issues?labels=${labels[1].name}`
      ).then((res) => res.json()),
    {
      enabled: !!labels, // ❗️❗️❗️该配置项在value为true时,会触发接口请求。因此当第一个接口请求返回后,此时该配置项表达式为true,会触发请求github中的issue列表
    }
  );

  return (
    

标签

{labelsQuery.isLoading ? (

加载标签中...

) : (
    {labelsQuery.data.map((label) => (
  • {label.name}
  • ))}
)}
// 下面的代码判断了在查询函数处于摸鱼状态时,不显示任何内容 ② {issuesQuery.isLoading && issuesQuery.fetchStatus === 'idle' ? null : (

Issues {Array.isArray(issuesQuery.data) ? `(${labels[1].name})` : ''}

// 当查询函数处于干活状态时,显示加载issues中 ③ {issuesQuery.isLoading ? (

加载issues中...

) : (
    {issuesQuery.data.map((issue) => (
  • {issue.title}
  • ))}
)}
)}
); }; export default function App() { return (
); }

在代码②中,当查询函数处于摸鱼状态时,不显示任何内容。在代码③中的isLoading是查询函数正在在干活的时候,此时我们把加载issues中...状态显示出来。

总结

  • 并行请求

    • 通过在查询函数中,使用Promise.all来进行接口的合并请求
    • 通过useQueries来进行动态的并行请求
  • 依赖请求

    • isLoadingtrue表示第一次请求未返回结果前这段时间的状态,如果对接口进行了禁用,可以通过fetchStatusidle来获取接口禁用请求这段时间的状态。

你可能感兴趣的:(react.js前端请求接口)