React Query

目的

也就是解决了什么问题?

常规流程

请求数据 =>加载中 => 后端返回 => 如果有报错展示报错 =>刷新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import * as React from "react";

export default function App() {
// 存储 后端返回数据
const [zen, setZen] = React.useState(""); // 存储 加载状态
const [isLoading, setIsLoading] = React.useState(false); // 存储 是否请求成功
const [isError, setIsError] = React.useState(false); // 存储 后端返回的错误数据
const [errorMessage, setErrorMessage] = React.useState("");

const fetchData = () => {
// 开始获取数据,将isLoading置为true
setIsLoading(true);

fetch("https://api.github.com/zen")
.then(async (response) => {
// 如果请求返回status不为200 则抛出后端错误
if (response.status !== 200) {
const { message } = await response.json();

throw new Error(message);
}

return response.text();
})
.then((text: string) => {
// 请求完成将isLoading置为false
setIsLoading(false); // 接口请求成功,将isError置为false
setIsError(false); // 存储后端返回的数据
setZen(text);
})
.catch((error) => {
// 请求完成将isLoading置为false
setIsLoading(false); // 接口请求错误,将isError置为true
setIsError(true); // 存储后端返回的错误数据
setErrorMessage(error.message);
});
};

React.useEffect(() => {
// 初始化请求数据
fetchData();
}, []);

return (
<div>
     <h1>Zen from Github</h1>     <p>
{isLoading ? "加载中..." : isError ? errorMessage : zen}
</p>   {" "}
{!isLoading && (
<button onClick={fetchData}>{isError ? "重试" : "刷新"}</button>
)}
   
</div>
);
}
  • 使用 isLoading 来存储加载状态
  • 使用 isError 来存储接口是否有错误
  • 使用 errorMessage 来存储后端返回的报错信息
  • 使用 zen 来存储后端返回数据存储
  • 重新调用 fetchData 方法来刷新数据

修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import * as React from "react";
import { useQuery } from "react-query";

const fetchData = () => {
return fetch("https://api.github.com/zen").then(async (response) => {
// 如果请求返回status不为200 则抛出后端错误
if (response.status !== 200) {
const { message } = await response.json();

throw new Error(message);
}

return response.text();
});
};

export default function App() {
const zenQuery = useQuery(["zen"], fetchData); // ①

return (
<div>
     <h1>Zen from Github</h1>     <p>
     {" "}
{zenQuery.isLoading || zenQuery.isFetching
? "加载中..."
: zenQuery.isError
? zenQuery.error?.message
: data}
     
</p>   {" "}
{!zenQuery.isLoading && !zenQuery.isFetching && (
<button
onClick={() => {
zenQuery.refetch();
}}
>
        {zenQuery.isError ? "重试" : "刷新"}       
</button>
)}
   
</div>
);
}

结果

为了解决这么多大量冗杂的模板代码.使用 react-query 会变得十分整洁.
上面各种 loading,error,数据刷新等等都交给 react-query 处理.

初始配置

将组件包裹在QueryClientProvider中,和 useContext 搭配就是把 useContext 实例化的组件放在其中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import {
QueryClient,
QueryClientProvider,
useQuery,
} from "@tanstack/react-query";

const queryClient = new QueryClient();

function Example() {
const query = useQuery("todos", fetchTodos);

return (
<div>
{query.isLoading
? "Loading..."
: query.isError
? "Error!"
: query.data
? query.data.map((todo) => <div key={todo.id}>{todo.title}</div>)
: null}
</div>
);
}

function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
);
}

搭配 useContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { ReactNode } from "react";
import { AuthProvider } from "./auth.context";
import { QueryClient, QueryClientProvider } from "react-query";

export const AppProviders = ({ children }: { children: ReactNode }) => {
return (
<QueryClientProvider client={new QueryClient()}>
<AuthProvider>{children}</AuthProvider>
</QueryClientProvider>
);
};

//auth.context
import * as auth from "auth-provider";
import { FullPageLoading } from "components/lib";
import { createContext, ReactNode, useContext } from "react";
import { useQueryClient } from "react-query";
import { User } from "types/user";
import { useMount } from "utils";
import { http } from "utils/http";
import { useAsync } from "utils/use-async";
import { FullPageErrorFallback } from "../components/lib";

const AuthContext = createContext<
| {
user: User | null;
login: (form: AuthForm) => Promise<void>;
register: (form: AuthForm) => Promise<void>;
logout: () => Promise<void>;
}
| undefined
>(undefined);

AuthContext.displayName = "AuthContext";

interface AuthForm {
username: string;
password: string;
}

// 启动时重置token
const bootstrapUser = async () => {
let user = null;
const token = auth.getToken();
if (token) {
const data = await http("me", { token });
user = data.user;
}
return user;
};

export const AuthProvider = ({ children }: { children: ReactNode }) => {
// const [user, setUser] = useState<User | null>(null);

const {
run,
isIdle,
isLoading,
isError,
error,
data: user,
setData: setUser,
} = useAsync<User | null>();
const queryClient = useQueryClient();

const login = (form: AuthForm) => auth.login(form).then(setUser);
const register = (form: AuthForm) => auth.register(form).then(setUser);
const logout = () =>
auth.logout().then(() => {
setUser(null);
// 清除usequery获取的数据
queryClient.clear();
});

// 挂载时重置user
useMount(() => {
run(bootstrapUser());
});

if (isIdle || isLoading) {
return <FullPageLoading />;
}
if (isError) {
return <FullPageErrorFallback error={error} />;
}

return (
<AuthContext.Provider
children={children}
value={{ user, login, register, logout }}
/>
);
};

export const useAuth = () => {
const context = useContext(AuthContext);

if (!context) {
throw new Error("useAuth必须在AuthProvider中使用");
}
return context;
};

常用方法

查询键和查询函数

1
const zenQuery = useQuery(["zen"], fetchData);
  • 其中['zen']就是 react-query 的查询键,react-query 通过不同的查询键来标识(映射)不同接口(或是同一接口不同参数请求)返回的数据。在 react-query@4 中,查询键必须是数组。
  • fetchData就是我们请求后端接口的函数,也就是查询函数.

    PS:查询键内的元素可以是嵌套数组、对象、字符串、数字
    例如:[‘zen’, { form: ‘confucius’ }]或[‘zen’, [‘confucius’, ‘Lao Tzu’]]

为了方便记忆,打个比方,你可以将查询键看做是你存储 localStorage 时的 key,而 value 则是通过查询函数查询到数据后,将各种我们需要的状态数据存储进入 value
使用变量作为查询键的元素时,当变量的值变化后,react-query 将会重新调用 fetchData 方法,获取新的数据,并缓存到对应变量值为 key 的缓存中。

并行请求

同时多个请求就写多个 useQuery,合并请求就用 Promise.all 包裹.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 <p>加载数据中...</p>;
}

if (reposAndGistsQuery.isError) {
return <p>数据加载错误: {reposAndGistsQuery.error.message}</p>;
}

if (!reposAndGistsQuery.data) {
return null;
}

const [repos, gists] = reposAndGistsQuery.data;

return (
<div>
<h2>仓库列表</h2>
<ul>
{repos.map((repo) => (
<li key={repo.id}>{repo.name}</li>
))}
</ul>

<hr />

<h2>代码片段列表</h2>
<ul>
{gists.map((gist) => (
<li key={gist.id}>{gist.description || "暂无描述"}</li>
))}
</ul>
</div>
);
};

export default ReposAndGists;

动态生成请求

useQueries.从动态获取[‘facebook’, ‘vuejs’, ‘nestjs’, ‘mongdb’],到重新批量获取了以下用户的仓库[‘microsoft’, ‘tesla’].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 (
<div>
     <h1>查看github用户的仓库</h1>     <button
onClick={() => setUsers(["microsoft", "tesla"])}
>
       更改获取用户      
</button>   {" "}
{userQueries.map((query) =>
query.isLoading ? (
<div>加载中....</div>
) : (
<ol>
         {" "}
{query.data.map((item) => (
<li>{item.full_name}</li>
))}
         
</ol>
)
)}
   
</div>
);
}

依赖请求

请求 B 接口的某个参数依赖 A 接口请求返回的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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 (
<div>
<h2>标签</h2>
{labelsQuery.isLoading ? (
<p>加载标签中...</p>
) : (
<ul>
{labelsQuery.data.map((label) => (
<li key={label.id}>{label.name}</li>
))}
</ul>
)}

<hr />

<h2>
Issues
{Array.isArray(issuesQuery.data) ? `(${labels[1].name})` : ""}
</h2>
{issuesQuery.isLoading ? (
<p>加载issues中...</p>
) : (
<ul>
{issuesQuery.data.map((issue) => (
<li key={issue.id}>{issue.title}</li>
))}
</ul>
)}
</div>
);
};

export default function App() {
return (
<div>
<IssueLabelFilter owner={"facebook"} repo={"react"} />
</div>
);
}

在 react-query 中,当该组件被加载时,组件内的 useQuery 就会开始请求。
此时明显不符合需求,需求的要求是在加载完标签列表后,获取到第二个标签,再开始请求 issues 列表。
因此就需要使用到 useQuery 的 enabled 参数,当参数值为 false 时,将会禁止请求接口。
现在回到上面的例子中,当 labelsQuery 请求还没有结果时,labels 变量值为 undefined,此时在 ① 行代码中的值为 false,当 labelsQuery 请求结束时,labels 变量值为数组,此时在 ① 行代码中的值为 true,issuesQuery 开始请求数据。完全符合需求的要求。

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

上面的例子中,issuesQuery 在加载后,由于处于禁用状态(配置项 enabled: false),此时 isLoading 将会一直处于 true 的状态,直到 issuesQuery 请求完成数据后变为 false。
这种提示会非常奇怪,明明有一段时间里 issuesQuery 没有真正的请求数据,为啥要一直显示加载标签中…的内容?
解决办法是:需要一个字段来区分查询函数当前并没有请求数据,处于摸鱼状态。
在 useQuery 中当 fetchStatus 字段在为 idle 时,表示当前查询函数不在运行,处于摸鱼状态^ ^

fetchStatus 一共有三个状态,fetching 状态表示当前查询函数正在运行,idle 状态表示当时查询函数不在运行。paused 状态表示查询函数尝试运行,但是无法进行请求,最可能的原因是由于当前没有联网,处于离线状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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 (
<div>
<h2>标签</h2>
{labelsQuery.isLoading ? (
<p>加载标签中...</p>
) : (
<ul>
{labelsQuery.data.map((label) => (
<li key={label.id}>{label.name}</li>
))}
</ul>
)}
<hr />
// 👇🏻下面的代码判断了在查询函数处于摸鱼状态时,不显示任何内容 ②{issuesQuery.isLoading &&
issuesQuery.fetchStatus === "idle" ? null : (
<div>
<h2>
Issues
{Array.isArray(issuesQuery.data) ? `(${labels[1].name})` : ""}
</h2>
// 当查询函数处于干活状态时,显示加载issues中 ③{issuesQuery.isLoading ? (
<p>加载issues中...</p>
) : (
<ul>
{issuesQuery.data.map((issue) => (
<li key={issue.id}>{issue.title}</li>
))}
</ul>
)}
</div>
)}
</div>
);
};

export default function App() {
return (
<div>
<IssueLabelFilter owner={"facebook"} repo={"react"} />
</div>
);
}

useMutation

使用useMutation进行增删除操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function App() {
const mutation = useMutation((newTodo) => axios.post("/todos", newTodo));

return (
<div>
{mutation.isLoading ? (
"Adding todo..."
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}

{mutation.isSuccess ? <div>Todo added!</div> : null}

<button
onClick={() => {
mutation.mutate({ id: new Date(), title: "Do Laundry" });
}}
>
Create Todo
</button>
</>
)}
</div>
);
}

mutate 函数是一个异步函数,这意味着你不能在事件回调中直接使用它 (React16 及之前版本)。 如果你需要在 onSubmit 中访问事件,则需要将 mutate 包装在另一个函数中。 这是由于 React 事件池限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在React16及之前的版本,这将无法正常工作
const CreateTodo = () => {
const mutation = useMutation((event) => {
event.preventDefault();
return fetch("/api", new FormData(event.target));
});

return <form onSubmit={mutation.mutate}>...</form>;
};

// 这将正常工作
const CreateTodo = () => {
const mutation = useMutation((formData) => {
return fetch("/api", formData);
});
const onSubmit = (event) => {
event.preventDefault();
mutation.mutate(new FormData(event.target));
};

return <form onSubmit={onSubmit}>...</form>;
};

重置修改状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const CreateTodo = () => {
const [title, setTitle] = useState("");
const mutation = useMutation(createTodo);

const onCreateTodo = (e) => {
e.preventDefault();
mutation.mutate({ title });
};

return (
<form onSubmit={onCreateTodo}>
{mutation.error && (
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
)}
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>

<button type="submit">Create Todo</button>
</form>
);
};

总结

  • 并行请求

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

  • 依赖请求

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

缓存状态

react-query 通常在挂载组件时获取数据;在获取数据后,将数据存储到缓存中,并将该数据提供给组件使用。
在获取数据时,有三种状态.loading,success,error.
也对应useQuery钩子中的isLoadingisSuccessisError属性
当 react-query 进行后端请求查询时,会有以下三个状态:

  • idle:空闲,表示当前不需要从后端获取数据
  • fetching: 获取数据,表示当前正在从后端获取数据
  • paused:暂停,表示原本尝试从后端获取数据,但是通常由于未联网的原因导致暂停

fetchStatus将会在 idle、fetching、paused 这三个状态间经历循环

在 react-query 中 status 为loading状态(或者isLoading为 true)指的是第一次从后端获取成功之前的状态.
而 fetchStatus 为fetching状态(或者isFetching为 true)指的是每次从后端获取数据的加载状态(包含第一次获取数据)。

整个生命周期:
假如你使用 react-query 从 Github 的接口请求了 react 的 issue 列表,你此次的请求结果将会在 status 中标记为 success 或 error,或者从 isSuccess、isError 中判断请求成功或者失败。
请求后端数据成功后,在写入缓存时,此时的缓存状态是fresh(最新)状态,但是很快(默认过期时间是 0ms)就会变为stale(老旧)状态。
如果使用 react 的 issue 列表的每个组件都被卸载后,issue 列表数据的缓存状态将会被标记为inactive(不活跃)状态。此时数据将不会被删除,直到一段时间后(默认为 5 分钟),react-query 将会从缓存中删除该条数据。
在变为inactive(不活跃)状态之前,该条数据将会在fresh(最新)与stale(老旧)之间来回切换,同时接口请求状态也会在idlefetching 之间切换。

fresh 最新态和 stale 老旧态

react-query 是否会触发查询函数,并从后端接口获取数据,与缓存状态是:fresh(最新)状态或stale(老旧)状态有关。如果缓存状态是stale(老旧),表示该查询将会有资格重新获取,但如果缓存状态是fresh(最新)的,就不会重新获取。

如果使用 react 的 issue 列表的每个组件都被卸载后,issue 列表数据的缓存状态将会被标记为inactive(不活跃)状态。此时数据将不会被删除,直到一段时间后(默认为 5 分钟),react-query 将会从缓存中删除该条数据。
在变为inactive(不活跃)状态之前,该条数据将会在fresh(最新)与stale(老旧)之间来回切换,同时接口请求状态也会在idlefetching之间切换。

staleTime

在默认情况下,后端返回的数据其缓存状态将会立即({staleTime: 0})fresh(最新)状态变为stale(老旧)状态。
其实这样做不难理解,因为当你请求到数据后,后端的数据有可能就发生了变化,因此当拿到后端返回的数据瞬间,缓存状态就是stale(陈旧)的了。
你可以将配置中的staleTime,设置一个毫秒数的数字,那么缓存将会在staleTime毫秒后过期(从fresh(最新)变为stale(陈旧))
如果把staleTime设置为Infinity,表示当前查询的数据将只会获取一次,且会在整个网页的生命周期内缓存。

触发条件

在 react-query 中并不是缓存从 fresh(最新)转换为 stale(老旧)状态时,就会重新获取。

  1. 当组件首次加载,将会触发数据的获取。如果组件被卸载后再次被加载,此时也会触发数据的重新获取。
  2. 当用户把浏览器重新聚焦,或者切换到当前选项卡。这个触发条件是默认开启的,如果希望关闭这个触发条件,可以把refetchOnWindowFocus选项设置为 false 来禁止。
  3. 网络重新连接。可以使用refetchOnReconnect来禁止。
  4. 定时刷新。当你在配置中设置refetchInterval为数字(代表 xxx 毫秒)时。无论此时数据是fresh(最新)还是stale(老旧)的缓存状态,react-query 都会在你设置的毫秒时间间隔内重新获取数据

清理缓存

在缓存状态处于inactive(不活跃)状态或者使用这个查询数据的组件卸载时,超过 5 分钟(默认情况下)后,react-query 将会自动清除该缓存。
如果你希望自定义这个时间,你可以使用cacheTime配置,下面的例子中将cacheTime设置为 0,实现的效果是,当查询数据在inactive状态时,立即从缓存中删除。

1
2
3
4
5
6
const userQuery = useQuery(
["user", username],
() =>
fetch(`https://api.github.com/users/${username}`).then((res) => res.json()),
{ cacheTime: 0 }
);

处理错误

错误重试

当一个查询无法重新获取时,将会基于指数退避算法的时间间隔,尝试请求 3 次。 从 1s 的延迟起步,到 30s 的最大延迟。下面是默认重试策略的相关配置:

1
2
3
4
5
6
const exampleQuery = useQuery("example", fetchExample, {
retry: 3,
retryDelay: (attempt) => {
return Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000);
},
});

PS: 如果只配置了 retry 的次数,那么 retry 的时间间隔,将会默认采用指数退避算法。

错误边界

使用react-error-boundary这个包,来减少相关错误边界的配置。
需要在配置项中,配置useErrorBoundarytrue,之后错误边界就能捕获到相关的报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import * as React from "react";
import "./style.css";
import { ErrorBoundary } from "react-error-boundary";
import { useQuery } from "react-query";

const fetchWithError = async (url, options = {}) => {
const response = await fetch(url, options);

let errorMessage = "";
if (response.status !== 200) {
errorMessage += `请求错误状态码为: ${response.status}. `;
}

const body = await response.json();
if (body.message) {
errorMessage += body.message;
}

console.log(errorMessage);

if (errorMessage) {
throw new Error(errorMessage);
}

return body;
};

const Repos = () => {
const reposQuery = useQuery(
["users"],
() => fetchWithError("https://api.github.com/users/facebook/repos"),
{
useErrorBoundary: true,
onError: (error) => console.log(error, "onError"),
}
);

return <div>{JSON.stringify(reposQuery.data)}</div>;
};

const Gists = () => {
const gistsQuery = useQuery(
["gists"],
() => fetchWithError("https://api.github.com/users/facebook/gists"),
{
useErrorBoundary: true,
onError: (error) => console.log(error, "onError"),
}
);

return <div>{JSON.stringify(gistsQuery.data)}</div>;
};

function QueryError({ error }) {
return (
<div>
<h1>出现错误</h1>
<p>{error.message}</p>
</div>
);
}

export default function App() {
return (
<ErrorBoundary FallbackComponent={QueryError}>
<Repos />
<Gists />
</ErrorBoundary>
);
}

错误回调

在 react-query 中,你可以在配置中设置 onError 属性,来获取错误的回调。包含了 error 的数据。
你可以在回调中,调用消息弹窗来提示报错信息,如果你使用 ant-design 组件库,你可以这么写:

1
2
3
4
5
6
7
const reposQuery = useQuery(
["users"],
() => fetchWithError("https://api.github.com/users/facebook/repos"),
{
onError: (error) => message.error({ content: error.message }),
}
);

针对重新获取数据失败处理的方式

在第一次请求数据时,查询函数返回成功。在页面失焦后,重新聚焦,触发第二次请求时,查询函数返回失败。
如何处理?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import * as React from "react";
import { useQuery } from "react-query";

let count = 0;

export default function App() {
const mockQuery = useQuery(
["mock"],
async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (count === 0) {
count++;
return "成功";
} else {
throw new Error("失败");
}
},
{ retry: false }
);

return (
<div>
   {" "}
{mockQuery.isError ? (
<div className="error-message">{mockQuery.error.message}</div>
) : null}
    {mockQuery.isLoading ? "首次加载中..." : mockQuery.data}   
</div>
);
}

你可以根据不同的情况来指定你的显示策略:

  • 当重新获取数据失败后,你可以像上面的例子一样即显示缓存数据,又显示报错
  • 也可以判断 isError 字段为 true 时,只显示报错信息,隐藏缓存数据。

更多内容

建议看官方文档