当前位置: 首页 > article >正文

@Tanstack/vue-query 的使用介绍

@Tanstack/vue-query 的使用介绍

前言

在今年的vue conf 会议上,提到了vue-query这个库,这里对它的基本使用做一个介绍。

会议资料地址: https://vueconf.cn/

Tanstack-query的前身是react-query,是一个本地的服务端状态管理的库,常和“乐观更新”配合使用。它会在本地维护一个基于内存的全局状态管理,当缓存中有数据的时候,优先使用缓存数据来展示,可以让用户获得非常流畅的体验(前提是网络状态正常,如果网络很差,可能长时间显示错误的数据)。同时后台请求数据,来保证数据的正确性。

tanstack-query的优势:

https://tanstack.com/query/latest/docs/framework/vue/typescript

  • 专用的devtools
  • 自动缓存请求过的数据
  • 自动后台查询
  • 窗口聚焦自动重新获取
  • 轮询/实时查询
  • 并行查询
  • 关联查询
  • 支持mutation API
  • 分页查询
  • 无限滚动
  • 请求取消
  • 自动垃圾回收
  • 错误重试
  • 数据预取
  • 支持初始化数据和占位数据
  • 自动网络监控
  • SSR 支持

简单的demo

可以从官网获取线上demo进行测试。

https://codesandbox.io/s/github/tanstack/query/tree/main/examples/vue/basic?from-embed=&file=/src/App.vue

🎯 线上demo热更新响应速度比较慢,而且不可以post更改数据。

可以使用本地启动demo项目,增加了json-server,因此可以使用restful风格的api实现增删改查的功能。

demo地址:https://github.com/hekang456/vue-query-example

如果需要使用json-server发其他请求,参考https://github.com/typicode/json-server

使用方式

这里使用最简单的代码覆盖最常见的使用场景,其他场景见文档。

1、获取数据

使用useQuery获取数据,需要设置queryKeyqueryFnqueryKey只要是可以序列化的数组即可,但需要保证全局唯一;queryFn是一个返回Promise的函数。

import { useQuery } from '@tanstack/vue-query';

const { isPending, isError, isFetching, data, error, refetch } = useQuery({
	queryKey: ['posts'],
	queryFn: fetchPostsApi
});

在这里插入图片描述

当页面打开的时候,自动请求了 ["posts"]接口,注意看 devtools 中数据的状态,从 Fetching (获取中)变成了 Stale(已过期)。

这是默认的配置,数据获取之后立刻变成“已过期”,这样当触发了一些条件的时候就会立马重新请求,比如页面聚焦、页面重新变成活跃状态等。

2、页面聚焦/页面重新变成活跃重新请求数据

这里以页面重新变成活跃为例。

在这里插入图片描述

Posts.vue页面,切换到Post.vue页面,可以看到请求了 ["post", "1"]的数据, ["post", "1"] 也是从 Fetching 变成了 Stale,而["posts"]数据状态变成了inactive(非活跃)。

当点击back回到Posts.vue页面的时候,立刻展示了缓存中的数据,同时 ["posts"]对应的接口立马重新请求后端。因为数据没有变化,所以页面也没有改变,提供了流畅的体验。

3、自动缓存

我们以第一条数据为例

在这里插入图片描述

当数据请求过之后,就会缓存起来,当再次显示时,会直接展示缓存中的数据,同时取请求后端数据。如果数据没有变化,页面也没有改变;否则修改页面。

4、修改过期时间

默认情况下,数据请求后会立刻获取,但是某些数据,开发者明确知道它很长时间都不会更新,这时候就可以设置过期时间。

这里单独修改["posts"]的过期时间.

const { isPending, isError, isFetching, data, error, refetch } = useQuery({
	queryKey: ['posts'],
	queryFn: fetchPostsApi,
  // 一小时
	staleTime: 10 * 60 * 1000,
});

在这里插入图片描述

可以看到,["posts"]数据不再会立刻过期,哪怕页面重新变成活跃状态,也只是使用缓存数据渲染,而没有请求新的数据。

5、手动刷新

调用refresh方法,来手动刷新接口,这里以["posts"]接口为例。

Posts.vue文件中,增加一个按钮,调用uesQuery返回的refresh方法。

<button @click="refetch">refresh Posts</button>

在这里插入图片描述

每次调用refresh,都会发送一个新的请求,即使数据还是Fresh状态。

6、数据轮询

增加refetchInterval属性。如果配合enable属性,可以方便的控制轮询暂停和继续。见 9。

const { isPending, isError, isFetching, data, error, refetch } = useQuery({
	queryKey: ['posts'],
	queryFn: fetchPostsApi,
	staleTime: 10 * 60 * 1000,
  // 2s轮询一次接口
	refetchInterval: 2 * 1000
});

在这里插入图片描述

这是一个非常实用的功能,避免了手写轮询带来的复杂代码逻辑。

7、自动重试

如果接口报错,默认会自动重试,但是只在get接口生效。这是为了防止其他类型的请求会多次修改后端数据。

因为前端报错的原因是多种的,比如后端处理时间太长,导致前端超时报错。但只要请求发到了后端,就会执行操作。

为了演示这种情况,需要让接口报错。我们改一下fetchPostsApi,让它报错。

export const fetchPostsApi = async (): Promise<Post[]> =>
	await fetch('http://localhost:3000/posts').then((response) => {
		// return response.json();
		throw new Error('error');
	});

在这里插入图片描述

可以看到,如果接口报错,会默认重试三次,并且重试的间隔时间翻倍。在重试过程中,数据一致处于pending状态,最后变成了error状态。

8、数据预取

假设当数据移动到某一条post数据上时,用户大概率是会点击查看的。这时候可以对这条数据预取,用户点击时可以及时显示内容。

在每一条数据上都绑定一个mouseenter事件,实现这个功能需要queryClient来实现。

需要指定唯一的queryKey,这个keyPost.vue组件中的请求方法是一样的,所以二者可以共用一份缓存数据。

<script lang="ts" setup>
import { useQuery, useQueryClient } from '@tanstack/vue-query';
import { fetchPostsApi, fetchPostApi } from './fetch';

// ......
const queryClient = useQueryClient();
const prefetchPost = async (id: number) => {
	await queryClient.prefetchQuery({
		queryKey: ['post', id],
		queryFn: () => fetchPostApi(id)
	});
};
</script>

<template>
	<h1>Posts</h1>
  // ......
	<div v-else-if="data">
		<ul>
			<li v-for="item in data" :key="item.id">
				<a
					@mouseenter="prefetchPost(item.id)"
					@click="$emit('setPostId', item.id)"
					href="#"
					:class="{ visited: isVisited(item.id) }"
					>{{ item.title }}</a
				>
			</li>
		</ul>
	</div>
</template>

在这里插入图片描述

当打开详情时,数据是瞬间展示的,并没有loading的过程。

9、控制数据获取的时机

有时候我们不希望数据立刻获取,而是等到触发了某些条件的时候在获取。

比如,我们假设["post"]中的数据,不是进入页面就需要获取,而是一些条件满足后才可以请求。以一个变量为例。

<script>
const validate = ref(false)
const { isPending, isError, isFetching, data, error, refetch } = useQuery({
	queryKey: ['posts'],
	queryFn: fetchPostsApi,
  // 只有验证通过,才可以发请求
	enabled: validate,
});
</script>

<template>
<button @click="validate = true">validate</button>
</template>

在这里插入图片描述

开始时,请求处于disabled状态,没有发送,直到点击了按钮,请求才发出。

10、关联请求

依然是用enable属性来实现的,比如官方的例子中,只有userid属性,才去发出请求。

// Get the user
const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: () => getUserByEmail(email.value),
})

const userId = computed(() => user.value?.id)
const enabled = computed(() => !!user.value?.id)

// Then get the user's projects
const { isIdle, data: projects } = useQuery({
  queryKey: ['projects', userId],
  queryFn: () => getProjectsByUser(userId.value),
  enabled, // The query will not execute until `enabled == true`
})

11、并行请求

多个请求之间,默认是并行的,比如:

const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
const teamsQuery = useQuery({ queryKey: ['teams'], queryFn: fetchTeams })

同时,对于同一类请求,可以使用useQueries来并行请求。

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

12、分页请求

需要修改App.vue文件,把分页组件显示出来。具体代码可以看代码仓库中的写法。

<Post v-if="postId > -1" :postId="postId" @setPostId="setPostId" />
<!-- <Posts v-else :isVisited="isVisited" @setPostId="setPostId" /> -->
<PagePosts v-else />

在这里插入图片描述

可以看到,请求下一页,在请求新接口的过程中,数据依然是使用上一页的,而没有展示loading。请求成功后进行替换。

而点击上一页,会直接用缓存数据渲染,同时后台发出请求,如果数据一致,页面UI不发生变化。

这是乐观更新的一种实现。

13、修改数据

修改数据的功能比较简单,我们给列表增加一个数据。

修改App.vue文件,增加以下代码,调用useMutation

插入数据成功后,手动让["post"]数据缓存失效,这样的话,这个接口会重新发送请求。exact: true意思是精确匹配,否则数组中包含posts的所有缓存都会失效

<script lang="ts" setup>

import { addPostApi } from './fetch';
import { useMutation, useQueryClient } from '@tanstack/vue-query';

const queryClient = useQueryClient();

const { isLoading, mutate } = useMutation({
	mutationFn: addPostApi,
	onSuccess: () => {
		queryClient.invalidateQueries({ queryKey: ['posts'], exact: true });
	}
});

const addPost = () => {
	mutate({
		userId: 1,
		id: Date.now(),
		title: 'bbbbbb',
		body: 'bbbbbb'
	});
};
</script>

<template>
	<button @click="addPost">add one postId</button>
</template>

在这里插入图片描述

这里点击按钮后,发送了两个请求,第一个post请求,插入一条数据,第二个是onSuccess回调中让列表缓存失效,从而重新请求列表数据

🚀 从这里也可以看出来,App.vue文件中并没有Post.vue组件的数据,但是只要修改对应的queryKey,同样可以影响到Post.vue组件。这避免了数据逐级传递和父组件管理全部子组件数据带来的复杂度问题。

14、初始数据和占位数据

先看初始数据,initialData的数据被认为是有效的,可以存储在缓存中,为了显示效果,给它设置一个过期时间。

const { isPending, isError, isFetching, data, error, refetch } = useQuery({
	queryKey: ['posts'],
	queryFn: fetchPostsApi,
	staleTime: 1000 * 60,
	initialData: [
		{
			id: 10000,
			userId: 1,
			title: 'First Post',
			body: 'Hello World'
		}
	]
});

在这里插入图片描述

可以看到,数据不过期,就不会请求接口

再看一下占位数据的表现。

const { isPending, isError, isFetching, data, error, refetch } = useQuery({
	queryKey: ['posts'],
	queryFn: fetchPostsApi,
	staleTime: 1000 * 60,
	placeholderData: [
		{
			id: 10000,
			userId: 1,
			title: 'First Post',
			body: 'Hello World'
		}
	]
});

在这里插入图片描述

占位数据不会缓存到内存中,当真正的数据返回时,占位数据会被替换。

15、网络状态和数据状态

数据状态:

  • pending
  • success
  • error

网络状态:

  • fetching
  • paused
  • idle

🚁 数据状态和网络状态并不总是对应,数据状态对应的是缓存中的数据状态,网络状态对应的是当次网络请求。

举个例子:

1 假如["post"]接口对应的数据已经请求成功了,那么数据状态是success,网络状态是idle;如果这个请求被再次激活,数据状态依然是success,网络状态是fetching --> idle

2 假如数据状态是pending,这时候可能在网络请求过程中,网络状态是fetching,也可能网络中断,网络状态是paused


http://www.kler.cn/a/287134.html

相关文章:

  • 1.tree of thought (使用LangChain解决4x4数独问题)
  • 高阶云服务-ELB+AS
  • Docker入门之Windows安装Docker初体验
  • 源码分析Spring Boot (v3.3.0)
  • C++为函数提供的型特性——缺省参数与函数重载
  • css浮动用法
  • jQuery基础——开发插件
  • template<typename ... _Args>可变参数模板
  • LiveQing视频点播流媒体RTMP推流服务用户手册-分屏展示:单分屏、四分屏、九分屏、十六分屏、轮巡播放、分组管理、记录加载
  • 001集——CAD—C#二次开发入门——开发环境基本设置
  • 超详细步骤——Keil MDK-ARM 如何修改工程名字
  • 外媒:《黑神话》成功后 中国加大对游戏行业的关注度
  • 触摸传感器的工作原理
  • Windows TCP/IP IPv6 DDos远程蓝屏复现及修复(CVE-2024-38063)
  • MFC生成dll的区别
  • Linux2-Linux基础命令
  • Wireshark 4.4 重磅发布!具有重大增强功能
  • Celery 中,广播模式可以通过使用 RabbitMQ 的 fanout 交换机来实现
  • WorkPlusIM软件:助力企业实现个性化即时通讯平台
  • 单门店共享自习室小程序系统源码搭建对接门禁和空开api
  • 【Bug】Ubuntu22.04英伟达驱动安装失败,重启后服务器卡在进入系统/grub的页面
  • 东南大学研究生-数值分析上机题(2023)Python 6 常微分方程数值解法
  • CSS3中display显示属性
  • ChatGPT 3.5/4.0新手使用手册
  • SQL语句中模糊匹配LIKE和正则表达式之间有什么相同点和不同点
  • compser好用镜像