从jQuery到Vue3:Tab组件的前世今生,聊聊状态管理思路的演变
·
从jQuery到Vue3:Tab组件状态管理的进化之路
在电商详情页的"商品介绍/规格参数"切换区,或是后台系统的多面板导航栏里,Tab组件作为高频交互控件,其实现方式折射出前端技术范式的三次革命。2006年jQuery的 addClass() 与 show() 统治了DOM操作,2014年Vue带来的响应式编程颠覆了状态管理逻辑,而2020年Composition API则让组件逻辑真正实现了原子化组合。我们以这个微观交互为切片,观察前端开发如何从"操作DOM"进化到"管理状态"。
1. jQuery时代:DOM为中心的 imperative 编程
<!-- 典型jQuery Tab结构 -->
<div class="tabs">
<ul class="nav">
<li class="active">Tab1</li>
<li>Tab2</li>
</ul>
<div class="content">
<div style="display:block">Content1</div>
<div style="display:none">Content2</div>
</div>
</div>
状态存储位置 :DOM元素的class和style属性本身就是状态容器。激活态Tab的索引值 0 被编码在 .active 类名和 display:block 样式里。
典型jQuery实现需要手动处理三个关注点:
- 事件绑定 :通过
.on('click')捕获交互 - 状态切换 :用
removeClass()/addClass()更新UI状态 - 内容联动 :用
hide()/show()控制内容显隐
// 经典jQuery实现
$('.nav li').on('click', function() {
$(this).addClass('active')
.siblings().removeClass('active');
$('.content > div').eq($(this).index())
.show().siblings().hide();
});
架构缺陷分析 :
- 状态分散 :激活索引同时存在于DOM类名、样式和事件处理逻辑中
- 难以测试 :必须构造完整DOM环境才能验证逻辑
- 维护成本 :新增Tab需要同步修改HTML结构和JS逻辑
2. Vue2/React时代:响应式数据驱动视图
当Vue的 v-model 和React的 useState 出现后,状态管理发生了本质变化:
<template>
<div>
<ul>
<li v-for="(tab, index) in tabs"
:class="{ active: activeIndex === index }"
@click="activeIndex = index">
{{ tab.title }}
</li>
</ul>
<div v-for="(tab, index) in tabs"
v-show="activeIndex === index">
{{ tab.content }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeIndex: 0,
tabs: [
{ title: 'Tab1', content: '...' },
{ title: 'Tab2', content: '...' }
]
}
}
}
</script>
状态存储位置 :显式声明的 activeIndex 变量成为唯一数据源,UI只是状态的映射。这种模式带来三个突破:
- 单向数据流 :用户交互 → 修改状态 → 自动更新UI
- 声明式渲染 :通过
v-show/v-if声明UI与状态的关系 - 逻辑内聚 :所有状态变更收敛到data/props中
对比jQuery方案的优势:
| 维度 | jQuery方案 | Vue/React方案 |
|---|---|---|
| 状态存储 | 分散在DOM属性中 | 集中在组件state |
| 测试难度 | 需要真实DOM | 可单独测试状态逻辑 |
| 可维护性 | 修改需触及多处 | 仅修改数据模型 |
| 可扩展性 | 新增功能易产生副作用 | 基于数据驱动天然可扩展 |
3. Vue3 Composition API:逻辑复用的新时代
Vue3的 setup 函数配合Composition API,使得Tab逻辑可以抽象为独立Hook:
// useTab.js
import { ref } from 'vue';
export function useTab(initialIndex = 0) {
const activeIndex = ref(initialIndex);
const setActiveIndex = (index) => {
activeIndex.value = index;
};
return { activeIndex, setActiveIndex };
}
组件中使用:
<template>
<!-- 模板与Vue2相同 -->
</template>
<script>
import { useTab } from './useTab';
export default {
setup() {
const tabs = reactive([...]);
const { activeIndex, setActiveIndex } = useTab();
return { tabs, activeIndex, setActiveIndex };
}
}
</script>
架构升级点 :
- 逻辑与UI解耦 :Tab切换逻辑可独立存在,不依赖组件实例
- 类型友好 :配合TypeScript能获得完整类型推导
- 可组合性 :可与其他Hook(如
useFetch)自由组合
// 更复杂的组合案例
const { data } = useFetch('/api/tabs');
const { activeIndex } = useTab();
const processedData = computed(() => process(data.value));
4. 现代状态管理的最佳实践
在大型项目中,我们还需要考虑:
多组件共享状态 :
// 在Pinia/Vuex中管理激活状态
export const useTabStore = defineStore('tabs', {
state: () => ({ activeIndex: 0 }),
actions: {
setActive(index) {
this.activeIndex = index;
}
}
});
TypeScript强化 :
interface Tab {
title: string;
content: string;
disabled?: boolean;
}
const tabs = ref<Tab[]>([...]);
无障碍支持 :
<li role="tab"
:aria-selected="activeIndex === index"
@keydown.enter="setActive(index)">
性能优化方案对比:
| 方案 | 适用场景 | 实现方式 |
|---|---|---|
| v-if | 内容切换频率低 | 销毁/重建DOM |
| v-show | 频繁切换 | CSS切换display属性 |
<KeepAlive> |
需要保留组件状态 | 动态组件缓存 |
| 虚拟滚动 | 超长列表 | 只渲染可视区内容 |
从jQuery到Vue3,我们看到前端开发的核心转变:
- 关注点分离 :从操作DOM到管理状态
- 抽象层级提升 :从命令式代码到声明式编程
- 复用能力增强 :从代码复制到逻辑组合
更多推荐


所有评论(0)