当前位置: 首页 > 工具软件 > react-query > 使用案例 >

react-query

濮阳烨然
2023-12-01

React Query

概览

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

快速入门

查询 Queries

查询是一种对于与 唯一键值 相关联的异步数据源的声明性依赖。查询可以与任何基于Promise的方法(包括GET和POST方法)一起使用,以从服务器获取数据。如果您的方法修改了服务器上的数据,建议改用 Mutations

要在你的组件或自定义 Hook 中订阅一个查询,至少需要以下的参数来调用useQueryHook:

  • 该查询的一个 唯一的键值
  • 一个返回Promise的函数
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,在其他任何状态下,如果查询在获取中(包含后台重新获取数据),则isFetchingtrue

对于大多数查询,通常先检查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']
数组键值

当查询需要更多信息来 唯一地描述其数据时,可以使用带有字符串的数组和任意数量的可序列化对象来描述。

使用场景

  • 分层或嵌套的资源
    • 传递ID、索引或者其他原语来唯一地标识资源地每一项是很常见的
  • 代由附加参数的查询
    • 传递作为附加选项的对象也很常见
// 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));
}

useQuery的第三个参数——配置项

enable:布尔值判断useQuery是否执行

useQuery('records',() => {
	....
},{
	enable:xxxx
})

获取缓存数据

queryClient.getQueryData(键名)

修改 Mutations

与查询不同,修改通常意味着用于 创建/更新/删除数据或执行服务器命令等副作用。为此,React Query导出了useMutationhook

【示例】

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>
  );
}

在任何给定时刻,修改只能处于以下状态之一:

  • isIdlestatus === 'idle' - 修改目前处于闲置状态或处于全新/重置状态
  • isLoadingstatus === 'loading' - 修改目前正在进行操作
  • isErrorstatus === 'error' - 修改遇到了错误
  • isSuccessstatus === '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 调用时被调用。

请注意,传递给 useMutationmutationFn同步的。在这种情况下,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的修改
    },
  });
});

Promises

asyncawait的语法糖,获取回调返回的参数

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')	//在提交成功/失败之后都进行重新查询更新状态
   },
 })

匹配正在提交的mutaion

await queryClient.isMutating() 获取正在提取的mutaion数量

 await queryClient.isMutating({
 	exact:true, 	// 是否精确匹配mutationKey
 	fetching:true,	//正在抓取的mutation,否则匹配未正在抓取的mutation
 	predicate:(mutation)=>布尔值,	//匹配返回true的mutation
 	mutationKey://匹配key
 })

持久化

  1. 导入

    import { dehydrate, hydrate} from 'react-query/hydration'
    
  2. 将mutation存进缓存中

    queryClient.setMutationDefaults(key,{
        mutationFn:提交修改请求并返回结果的方法,
        //...生命周期方法
    })
    
  3. 提交

    const mutation = useMutation(key);
    mutation.mutate(参数)
    
  4. 设备离线时,将被暂停的提交去水

    const state = dehydrate(queryClient)
    
  5. 设备开启时,将提交水化然后重新提交

    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();

查询错误处理 Query Invalidation

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);

typedone的查询会被无效,但是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);
 类似资料: