React Query 通常被描述为 React 缺少的数据获取(data-fetching)库,但是从更广泛的角度来看,它使 React 程序中的获取,缓存,同步和更新服务器状态变得轻而易举。
在下面的例子中,你可以看到 React Query 以其最基本和简单的形式被用来获取 GitHub 项目本身的 React Query 的统计信息:
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
);
}
function Example() {
const { isLoading, error, data } = useQuery("repoData", () =>
fetch("https://api.github.com/repos/tannerlinsley/react-query").then(
(res) => res.json(),
),
);
if (isLoading) return "Loading...";
if (error) return "An error has occurred: " + error.message;
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong> {data.subscribers_count}</strong>{" "}
<strong>✨ {data.stargazers_count}</strong>{" "}
<strong> {data.forks_count}</strong>
</div>
);
}
npm install react-query
或
yarn add react-query
查询是一种对于与 唯一键值 相关联的异步数据源的声明性依赖。查询可以与任何基于Promise的方法(包括GET和POST方法)一起使用,以从服务器获取数据。如果您的方法修改了服务器上的数据,建议改用 Mutations
要在你的组件或自定义 Hook 中订阅一个查询,至少需要以下的参数来调用useQuery
Hook:
import { useQuery } from "react-query";
function App() {
const info = useQuery("todos", fetchTodoList);
}
这个唯一键值将在内部用于在整个程序中重新获取数据、缓存和共享查询
info
对象包含一些非常重要的状态,这些状态可以提高工作的效率。在任何给定时刻,查询只能处于以下状态之一:
isLoading
或者status === 'loading'
,查询没有数据,正在获取结果中isError
或者status == 'error'
,查询遇到一个错误isSuccess
或者status === 'success'
,查询成功,并且数据可用isIdle
或者status === 'idle'
,查询处于禁用状态除了这些主要状态以外,取决于具体查询的状态,还有更多信息可用:
error
,如果查询处于isError
状态,则可以通过error
属性获取该错误data
,如果查询处于success
状态,则可以通过data
属性获得数据isFetching
,在其他任何状态下,如果查询在获取中(包含后台重新获取数据),则isFetching
为true
对于大多数查询,通常先检查
isLoading
状态,然后检查isError
状态,最后假设数据可用并呈现成功状态即可
function Todos() {
const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList);
if (isLoading) {
return <span>Loading...</span>;
}
if (isError) {
return <span>Error: {error.message}</span>;
}
// 现在我们可以假设 `isSuccess === true`
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
从本质上讲,React Query 基于查询键值为您管理查询缓存。 键值可以像字符串一样简单,也可以像由许多字符串和嵌套对象组成的数组一样复杂。
只要键值是可序列化的,并且对查询的数据来说它是唯一的即可
当传递一个字符串查询的时,它将在内部转换为以一个数组,其中字符串作为键值中的唯一项
// A list of todos
useQuery('todos', ...) // queryKey === ['todos']
// Something else, whatever!
useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']
当查询需要更多信息来 唯一地描述其数据时,可以使用带有字符串的数组和任意数量的可序列化对象来描述。
使用场景
// An individual todo
useQuery(['todo', 5], ...)
// queryKey === ['todo', 5]
// And individual todo in a "preview" format
useQuery(['todo', 5, { preview: true }], ...)
// queryKey === ['todo', 5, { preview: true }]
// A list of todos that are "done"
useQuery(['todos', { type: 'done' }], ...)
// queryKey === ['todos', { type: 'done' }]
这意味着,不管对象中键值的顺序如何,以下所有查询都被认为是相等的:
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
但是,以下查询键值不相等。数组项的顺序很重要
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
由于查询键值唯一地描述了它们要获取的数据,因此它们应包括您在查询函数中使用的任何需要更改的变量。例如:
function Todos({ todoId }) {
const result = useQuery(["todos", todoId], () => fetchTodoById(todoId));
}
enable:布尔值
判断useQuery
是否执行
useQuery('records',() => {
....
},{
enable:xxxx
})
queryClient.getQueryData(键名)
与查询不同,修改通常意味着用于 创建/更新/删除数据或执行服务器命令等副作用。为此,React Query导出了useMutation
hook
【示例】
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>
);
}
在任何给定时刻,修改只能处于以下状态之一:
isIdle
或 status === 'idle'
- 修改目前处于闲置状态或处于全新/重置状态isLoading
或 status === 'loading'
- 修改目前正在进行操作isError
或 status === 'error'
- 修改遇到了错误isSuccess
或 status === 'success'
- 修改是成功的,且数据可用除了这些主要状态之外,还有更多的信息可用,具体取决于修改的状态:
error
- isError
时,则可以通过 error
属性获取错误data
- isSuccess
时,则可以通过 data
属性获取数据在上面的示例中,您还看到可以通过使用单个变量或对象调用 mutate
函数来将变量传递给您的修改函数
【示例】
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>;
};
在某些情况下,你需要清除erro
或者修改请求的数据。我们可以使用rest
函数来处理
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)}
/>
<br />
<button type="submit">Create Todo</button>
</form>
);
};
useMutation
附带一些有帮助的选项。 允许在其生命周期的任何阶段快速而简单地产生副作用。
方法一
useMutation(fn,{
onMutate: variables => { variables为给fn传递的参数
发起提交回调
return {name:'jeff'}
},
onError: (error, variables, context) => { context为onMutate返回的内容
错误回调
},
onSuccess: (data, variables, context) => { data为提交请求后返回的数据
成功回调
},
onSettled: (data, error, variables, context) => {
在成功和失败回调之后触发的回调
},
})
方法二
mutation.mutate({
onSuccess: (data, variables, context) => {
...
},
onError: (error, variables, context) => {
...
},
onSettled: (data, error, variables, context) => {
...
}
})
当持续修改时,onSuccess
, onError
and onSettled
这几个回调会有一些细微的差别。
当组件是挂载状态,且将它们传递给 mutate
函数时,它们只会被 触发一次。 这是因为在每次调用 mutate
函数时,相关的观察者都会被先移除再重新监听。
与之相反,useMutation
的处理函数会在每一次的 mutate
调用时被调用。
请注意,传递给
useMutation
的mutationFn
是同步的。在这种情况下,mutationFn
的触发顺序和调用mutate
时的顺序是有所不同的。
useMutation(addTodo, {
onSuccess: (data, error, variables, context) => {
//调用三次
},
});
[("Todo 1", "Todo 2", "Todo 3")].forEach((todo) => {
mutate(todo, {
onSuccess: (data, error, variables, context) => {
// 只调用一次,且是对Todo3的修改
},
});
});
async
与await
的语法糖,获取回调返回的参数
const res=await mutation.mutateAsync参数);
默认情况下,React Query 不会在出错时重试修改,但可以使用 retry
选项:
const mutation = useMutation(addTodo, {
retry: 3,
});
如果由于设备离线而导致修改失败,那么当设备重新连接时,它们将以相同的顺序重新尝试。
在提交的生命周期中,先获取到旧值,若发生了错误,则还是将旧值缓存
useMutation(updateTodo, {
onMutate: async newTodo => {
await queryClient.cancelQueries('todos') //停掉所有可能在提交期间会覆盖掉数据的查询
const previousTodos = queryClient.getQueryData('todos') //获取缓存的旧值
return { previousTodos } //将旧值返回给上下文context
},
onError: (err,variables, context) => {
queryClient.setQueryData('todos', context.previousTodos) //若发生错误,则继续缓存旧值
},
onSettled: () => {
queryClient.invalidateQueries('todos') //在提交成功/失败之后都进行重新查询更新状态
},
})
await queryClient.isMutating()
获取正在提取的mutaion数量
await queryClient.isMutating({
exact:true, // 是否精确匹配mutationKey
fetching:true, //正在抓取的mutation,否则匹配未正在抓取的mutation
predicate:(mutation)=>布尔值, //匹配返回true的mutation
mutationKey://匹配key
})
导入
import { dehydrate, hydrate} from 'react-query/hydration'
将mutation存进缓存中
queryClient.setMutationDefaults(key,{
mutationFn:提交修改请求并返回结果的方法,
//...生命周期方法
})
提交
const mutation = useMutation(key);
mutation.mutate(参数)
设备离线时,将被暂停的提交去水
const state = dehydrate(queryClient)
设备开启时,将提交水化然后重新提交
hydrate(queryClient, state)
queryClient.resumePausedMutations()
【示例】
const queryClient = new QueryClient();
// 定义 "addTodo" 修改
queryClient.setMutationDefaults("addTodo", {
mutationFn: addTodo,
onMutate: async (variables) => {
// 取消 todos list 当前的查询
await queryClient.cancelQueries("todos");
// 创建一个对于 todo 的乐观修改
const optimisticTodo = { id: uuid(), title: variables.title };
// 添加到 todos list
queryClient.setQueryData("todos", (old) => [...old, optimisticTodo]);
// 返回包含乐观修改的上下文
return { optimisticTodo };
},
onSuccess: (result, variables, context) => {
// 成功,用正确内容替换掉
queryClient.setQueryData("todos", (old) =>
old.map((todo) =>
todo.id === context.optimisticTodo.id ? result : todo,
),
);
},
onError: (error, variables, context) => {
// 清除掉添加失败的 todo
queryClient.setQueryData("todos", (old) =>
old.filter((todo) => todo.id !== context.optimisticTodo.id),
);
},
retry: 3,
});
// 在同一个组件内启动修改
const mutation = useMutation("addTodo");
mutation.mutate({ title: "title" });
// 如果因为设备离线而暂停了修改,
// 然后,当程序退出时,可以使暂停的修改变为 dehydrated 的
const state = dehydrate(queryClient);
// 当程序启动时,修改再次启动
hydrate(queryClient, state);
// 重启修改
queryClient.resumePausedMutations();
QueryClient
包含一个 invalidateQueries
方法,可以智能地将查询标记为过时的,并使之可用重新获取数据
// 使缓存中的每个查询都无效
queryClient.invalidateQueries();
// 无效每个查询键值以 `todos` 开头的查询
queryClient.invalidateQueries("todos");
当使用 invalidateQueries
使查询无效时,会发生两件事:
useQuery
或相关 hook 中使用的所有 staleTime
配置useQuery
或相关 hook 渲染,则该查询也会在后台重新获取数据我们可以通过将更特定的键值传递给 invalidateQueries
方法来使具有特定变量的查询无效:
queryClient.invalidateQueries(["todos", { type: "done" }]);
// 该查询会被无效
const todoListQuery = useQuery(["todos", { type: "done" }], fetchTodoList);
// 该查询不会被无效
const todoListQuery = useQuery("todos", fetchTodoList);
type
为done
的查询会被无效,但是todos
中仍存在type
不为done
的数据,所以查询仍有效
invalidateQueries
API 非常灵活,因此,即使您只想使不再具有任何变量或子键的 todos
查询无效, 也可以将 exact: true
选项传递给 invalidateQueries
方法:
queryClient.invalidateQueries("todos", { exact: true });
// 该查询会被无效
const todoListQuery = useQuery(["todos"], fetchTodoList);
// 该查询不会被无效
const todoListQuery = useQuery(["todos", { type: "done" }], fetchTodoList);