当 el-tabs
遇上"切换焦虑":数据渲染的"小混乱"
el-tabs
让用户能轻松地来回切换,可以在一个页面显示更多的内容,方便用户查看自己想要的内容。 但是当我们觉得一切很顺利是,却忽然发现它也会闹出一些小情绪,引发数据渲染的"小混乱"。今天,咱们就来聊聊这个有趣又让人头疼的话题。
如果每个标签页的内容组件都是一些简单的静态内容,或者是一次请求所有标签的内容,那倒不会出什么问题。可一旦这些组件里涉及到异步数据请求、复杂的状态管理,事情就变得有趣起来了。
数据渲染的"小混乱"是如何诞生的?
异步请求的"撞车"事件
想象一下,你正在看标签页 A 的内容,这个页面需要从服务器请求一些数据来展示。你耐心地等着数据加载完成,可就在这个时候,你突然心血来潮,切换到了标签页 B。标签页 B 一看,嘿,我这儿也需要从服务器拿数据,于是也赶紧发起了请求。
等一会儿,标签页 B 的数据先回来了,它兴冲冲地更新了自己的内容。可没过多久,标签页 A 的数据也回来了,它也不管你现在看的是标签页 B,直接就把自己的内容更新了。这就导致了一个很尴尬的情况:你明明在看标签页 B,可它的内容却被标签页 A 的数据给覆盖了。这就像是两辆车在路口"撞车"了,谁也不让谁,结果大家都乱了套。
一个直观的示例
为了更直观地理解这个问题,我们来看一个简单的示例代码。在这个例子中,每个标签页的内容组件都会发起一个异步请求来加载数据。我们使用 setTimeout
来模拟异步请求,每个标签页的延迟时间不同,以便更好地理解延迟高的数据覆盖延迟低的数据的场景。

为了更直观的理解,贴上造成这场"小混乱"的代码。
在点击 tab 时实时请求数据,使用 setTimeout 来模拟异步请求,每个 tab 的延迟时间设置为100ms、1000ms、500ms、2000ms,这样可以更好的理解延迟高的数据覆盖延迟低的数据的场景,妥妥的"后来居上"。
typescript
<script setup lang="ts">
import type { TabsPaneContext } from "element-plus";
import { ref,onMounted } from "vue";
const tabs = [
{ label: "Tab 1", name: "1" },
{ label: "Tab 2", name: "2" },
{ label: "Tab 3", name: "3" },
{ label: "Tab 4", name: "4" },
];
const activeTab = ref("1");
const data = ref("");
const fetchData = async () => {
if (activeTab.value === "1")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 1"), 100)
);
if (activeTab.value === "2")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 2"), 1000)
);
if (activeTab.value === "3")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 3"), 500)
);
if (activeTab.value === "4")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 4"), 2000)
);
};
const setActiveTab = async (tab: TabsPaneContext) => {
activeTab.value = tab.paneName as string;
data.value = await fetchData() as string;
};
onMounted(() => {
setActiveTab({ paneName: activeTab.value });
});
</script>
<template>
<div>
<el-tabs v-model="activeTab" @tab-click="setActiveTab">
<el-tab-pane
v-for="(tab, index) in tabs"
:key="index"
:label="tab.label"
:name="tab.name"
>
<div>{{ data }}</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
如何避免这场"小混乱"?
1. 独立状态管理
每个标签页的内容组件应独立管理自己的状态,避免依赖全局状态。这种方法类似于为每个组件分配一个独立的"数据容器",确保它们互不干扰。通过这种方式,即使一个标签页修改了自己的状态,也不会影响到其他标签页。这类似于给每个用户分配一个独立的"物品",避免了因共享资源导致的冲突。
2. 设置标志符,确保只使用对应请求数据
通过设置一个标识符(如 currentTab
),在切换标签页时取消之前的请求,只使用本次点击的标签页发出的请求的数据来渲染 tabPane
。这种方法可以确保数据的正确性和一致性,避免因请求"撞车"导致的数据混乱。
3. 取消未完成的请求
通过 axios
的 CancelToken
或其他方式取消未完成的请求,只保留当前点击的请求获取的数据。这种方法可以确保不会因未完成的请求干扰当前标签页的数据渲染。
4. 使用独立组件管理每个标签页
将每个标签页的内容封装为独立组件,每个组件管理自己的状态和数据请求。这种方法可以有效避免因共享状态导致的数据混淆,确保每个标签页的数据独立性。
5. 合理管理异步请求
使用像 TanStack Query 这样的库来管理异步请求。这些库提供了强大的请求管理功能,可以有效避免请求"撞车"问题。它们会自动处理请求的状态,并确保数据正确地更新到对应的组件中。
... 更多解决方案,不再赘述。
重点说说------使用TanStack Query来管理异步请求
上述几种避免混乱的方案,取消请求的实现稍微复杂一些,其次对大家来说比较陌生的就是TanStack Query,实际上使用它来处理 el-tabs 数据混淆问题,非常简单,在此着重说一说。
以下是使用TanStack Query优化后的代码
xml
<script setup lang="ts">
import { useQuery } from "@tanstack/vue-query";
import type { TabsPaneContext } from "element-plus";
import { ref } from "vue";
const tabs = [
{ label: "Tab 1", name: "1" },
{ label: "Tab 2", name: "2" },
{ label: "Tab 3", name: "3" },
{ label: "Tab 4", name: "4" },
];
const activeTab = ref("1");
const fetchData = async () => {
if (activeTab.value === "1")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 1"), 100)
);
if (activeTab.value === "2")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 2"), 1000)
);
if (activeTab.value === "3")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 3"), 500)
);
if (activeTab.value === "4")
return new Promise((resolve) =>
setTimeout(() => resolve("Content 4"), 2000)
);
};
const setActiveTab = async (tab: TabsPaneContext) => {
activeTab.value = tab.paneName as string;
await refetch();
};
const { data,isFetching, refetch, isError } = useQuery({
queryKey: ["tabContent", activeTab.value],
queryFn: () => fetchData(),
enabled: activeTab.value==='1',
});
</script>
<template>
<div>
<el-tabs v-model="activeTab" @tab-click="setActiveTab">
<el-tab-pane
v-for="(tab, index) in tabs"
:key="index"
:label="tab.label"
:name="tab.name"
>
<div v-if="isFetching">Loading...</div>
<div v-else-if="isError">Error occurred</div>
<div v-else>{{ data }}</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
现在无论如何点击切换 4 个 tab,数据也是齐齐整整一丝不苟的渲染到对应的 tabPane 了。

为什么 TanStack Query 能有效避免数据混淆
- 独立缓存:每个标签页的数据请求都有独立的缓存,即使切换标签页也不会互相干扰。
- 后台更新:即使用户切换标签页,后台请求仍然会完成,并更新缓存,确保数据的一致性。
- 状态同步:所有使用相同查询键的组件都会自动同步到最新数据,避免了因状态不一致导致的混乱。
TanStack Query
作为一个非常强大的库,不仅可以解决异步请求的"撞车"问题,还可以提供更多的功能,如数据缓存、错误处理等,值得我们在实际项目中深入挖掘和应用。
总结
在实际开发中,类似el-tabs
数据渲染混乱的问题并不少见,尤其是在涉及异步数据请求和复杂状态管理时,比如进行数据筛选时,如果请求返回时间长,也容易导致数据的渲染错误。通过使用缓存机制、独立状态管理、合理管理异步请求、取消未完成的请求以及封装独立组件等方法,可以有效避免这些问题,提升用户体验。
希望这篇文章能帮助你更好地理解和解决 el-tabs
数据渲染混乱的问题。如果你在开发中遇到类似的问题,不妨尝试上述方法,相信会有所帮助。
欢迎交流开发过程中遇到的各种问题,让我们一起探索前端开发的更多可能性!
有任何问题或者兴趣也可以在公众号:自由前端之路 找到我。