引言
本文将通过一个简单的 Todos 应用,带你从传统 DOM 操作的"石器时代",穿越到 Vue3 响应式编程的"未来世界"。没有枯燥的理论,只有生动的对比和深入浅出的解析,让你真正理解 Vue3 为何成为现代前端开发的首选框架。
一、传统做法:我们曾经如何与 DOM "搏斗"
让我们先看一下这个简短的 demo.html 文件:
源码链接:vue/todos/demo.html · Zou/lesson_zp - 码云 - 开源中国
在 Vue、React 等现代框架出现之前,开发一个简单的 Todos 应用会是这样的:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2 id="app"></h2>
<input type="text" id="todo-input">
<script>
// 先找到DOM元素 命令式 机械的 性能差的
// JS(V8 快) -> HTML(渲染引擎 慢)
const app = document.getElementById('app')
const todoInput = document.getElementById('todo-input')
todoInput.addEventListener('change', function(event) {
const todo = event.target.value.trim();
if (!todo) {
console.log('请输入任务');
return;
}
// 渲染到页面
app.innerHTML = todo;
})
</script>
</body>
</html>
传统开发的痛点
-
手动 DOM 操作 :每次状态变化都需要手动更新 DOM,
getElementById、createElement、appendChild等操作冗长且易错 -
状态与视图分离 :数据 (
todos) 存储在一个地方,而视图渲染逻辑又在另一个地方,维护一致性困难 -
细粒度更新困难:即使只修改一个任务的状态,也需要重新渲染整个列表
-
事件处理分散:点击、键盘事件处理逻辑分布在各处,难以追踪
-
性能问题:频繁的 DOM 操作会导致页面重排重绘,影响性能
-
代码复用性差:逻辑与 DOM 操作紧密耦合,难以提取复用
传统开发就像手动操纵木偶表演:你需要控制每一根绳子(DOM 元素),一个动作需要协调多根绳子,复杂场景下极易出错且效率低下。
二、Vue3 新纪元:让数据驱动视图
现在,让我们进入 Vue3 的世界。以下是完整的 App.vue 代码,一字未改:
完整项目链接:vue/todos/demo.html · Zou/lesson_zp - 码云 - 开源中国
App.vue源码链接:vue/todos/vue3-todos/src/App.vue · Zou/lesson_zp - 码云 - 开源中国
html
<template>
<div>
<h1>{{ title }}</h1>
<h2>{{ title }}</h2>
<input type="text" v-model="title" @keydown.enter="addTodo"/>
<ul v-if="todos.length">
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{done:todo.done}">{{ todo.title }}</span>
</li>
</ul>
<div v-else>
暂无计划
</div>
<div>
全选<input type="checkbox" v-model="allDone">
{{ active }} 件未完成任务 / {{ todos.length }} 件总任务
</div>
</div>
</template>
<script setup>
// ref 响应式数据
import { ref } from 'vue';
// computed 用于计算属性
import { computed } from 'vue';
const title = ref("Todos任务清单");
const todos = ref([
{id: 1,title: '打王者',done: false},
{id: 2,title: '吃饭', done: true},
{id: 3,title: '睡觉',done: false},
{id: 4,title: '学习vue',done: false}
]);
// 将文本框内的内容添加到todos中
const addTodo = () =>{
if(!title.value) return;
todos.value.push({
id:Math.random(),
title:title.value,
done:false
})
title.value = '';
}
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length;
})
// 全选功能
const allDone = computed({
get(){
return todos.value.every(todo => todo.done);
},
set(value){
todos.value.forEach(todo => {
todo.done = value;
})
}
})
</script>
<style scoped>
.done {
color: gray;
text-decoration: line-through;
}
</style>
三、模板(Template):声明式 UI 的魔法
1. 双大括号插值 {``{ title }}
html
<h1>{{ title }}</h1>
<h2>{{ title }}</h2>
- 功能:将 JavaScript 表达式的结果渲染为文本
- 原理 :Vue 会创建一个响应式依赖,当
title变化时自动更新视图 - 类比:就像 Excel 中的单元格引用,当源数据变化时,所有引用它的地方自动更新
2. v-model:双向数据绑定的基石
html
<input type="text" v-model="title" @keydown.enter="addTodo"/>
<input type="checkbox" v-model="todo.done">
<input type="checkbox" v-model="allDone">
- 功能:在表单元素和组件上创建双向数据绑定
- 本质 :语法糖,等效于
:value="title" @input="title = $event.target.value" - 神奇之处 :
- 对于
<input type="text">,绑定value属性和input事件 - 对于
<input type="checkbox">,绑定checked属性和change事件 - 自动处理不同表单元素的细节,开发者无需关心
- 对于
v-model让我们摆脱了手动同步表单值与数据状态的繁琐工作,就像给数据和视图之间架起了一座自动传输桥。
3. 事件处理 @keydown.enter
html
<input type="text" v-model="title" @keydown.enter="addTodo"/>
@符号 :v-on:的简写,用于监听 DOM 事件.enter修饰符:仅在按下回车键时触发处理函数- 优势 :无需手动检查
event.key,Vue 内部已经处理了浏览器兼容性
Vue 还提供了其他有用的事件修饰符:
.stop:阻止事件冒泡.prevent:阻止默认行为.capture:使用捕获模式.self:只当事件是从元素本身触发时才处理.once:只触发一次
4. 条件渲染 v-if 和 v-else
html
<ul v-if="todos.length">
<!-- 待办事项列表 -->
</ul>
<div v-else>
暂无计划
</div>
- 功能:根据表达式值的真假条件性地渲染元素
- 特点 :
- 惰性渲染:初始渲染时条件为假,不会渲染该元素
- 切换开销高:会销毁和重建内部元素及其绑定的事件监听器
- 与
v-show的区别 :v-show始终渲染元素,只是通过 CSSdisplay属性切换v-if是真正的条件渲染,适合运行时条件不太可能改变的场景
5. 列表渲染 v-for 与 :key
html
<li v-for="todo in todos" :key="todo.id">
<!-- 每个待办事项的内容 -->
</li>
- 功能:基于源数据多次渲染元素或模板块
:key的重要性 :- 帮助 Vue 识别节点身份,高效地更新虚拟 DOM
- 确保组件状态在列表变动时保持正确
- 避免不必要的重新渲染,提升性能
- 最佳实践:使用唯一且稳定的标识符作为 key,避免使用数组索引
6. 动态绑定 :(v-bind 的简写)
html
<span :class="{done:todo.done}">{{ todo.title }}</span>
- 功能:动态绑定 HTML 属性、组件 props 或 CSS 类
- 对象语法 :
{done: todo.done}当todo.done为 true 时添加done类 - 数组语法 :也可以使用
:class="[isActive ? 'active' : '', errorClass]"等形式 - 其他常见用法 :
:id="dynamicId":disabled="isDisabled":style="{ color: activeColor, fontSize: fontSize + 'px' }"
四、脚本(Script Setup):Composition API 的力量
1. ref():响应式数据的基础
javascript
import { ref } from 'vue';
const title = ref("Todos任务清单");
const todos = ref([
{ id: 1, title: '打王者', done: false },
{ id: 2, title: '吃饭', done: true },
{ id: 3, title: '睡觉', done: false },
{ id: 4, title: '学习vue', done: false }
]);
- 功能:创建一个响应式引用对象
- 机制 :
- 返回一个带有
.value属性的对象 - Vue 通过 proxy 代理拦截对
.value的读取和设置 - 在模板中自动解包,无需使用
.value
- 返回一个带有
- 使用场景 :
- 基础类型数据(字符串、数字、布尔值)
- 需要在 setup() 之外访问或修改的响应式数据
- 对比
reactive():ref适合单个值或需要导出的属性reactive适合对象或数组,但解构会失去响应性
2. 事件处理函数
javascript
const addTodo = () => {
if(!title.value) return;
todos.value.push({
id: Math.random(),
title: title.value,
done: false
});
title.value = '';
}
- 重点:直接修改响应式数据,Vue 自动更新视图
- 无需手动 :
- 无需获取 DOM 元素
- 无需创建和插入新元素
- 无需更新统计信息
- 响应式原理 :Vue 会追踪
todos.value的变化,自动更新依赖它的模板部分
3. computed():派生状态的计算属性
javascript
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length;
});
-
功能:创建一个计算属性 ref
-
原理 :
- 缓存计算结果,仅当依赖变化时重新计算
- 自动追踪依赖(此处是
todos.value)
-
对比方法 :
javascript// 方法 - 每次重新渲染都会调用 const getActiveCount = () => todos.value.filter(t => !t.done).length; // computed - 仅当 todos 变化时重新计算 const activeCount = computed(() => todos.value.filter(t => !t.done).length); -
性能优势:避免不必要的重复计算,尤其在大型应用中
4. 高级 computed():getter 和 setter
javascript
const allDone = computed({
get() {
return todos.value.every(todo => todo.done);
},
set(value) {
todos.value.forEach(todo => {
todo.done = value;
});
}
});
- 双模式:计算属性可以同时具有 getter 和 setter
- getter :当读取
allDone.value时调用,计算全选状态 - setter :当设置
allDone.value = true/false时调用,更新所有任务状态 - 应用场景 :
- 表单验证
- 数据转换
- 状态派生
- 实现双向绑定的复杂逻辑
在这个 Todos 应用中,
allDone计算属性让我们的全选复选框能与任务列表状态完美同步,而无需额外的事件处理函数。这是 Vue 响应式系统的强大体现------当你描述清楚数据之间的关系,框架会负责其余的一切。
五、样式(Style):组件化 CSS
css
<style scoped>
.done {
color: gray;
text-decoration: line-through;
}
</style>
scoped属性:CSS 仅应用于当前组件- 实现原理 :
- Vue 编译器为组件模板中的所有元素添加唯一属性(如
data-v-f3f3eg9) - 为 CSS 选择器添加属性选择器,如
.done[data-v-f3f3eg9]
- Vue 编译器为组件模板中的所有元素添加唯一属性(如
- 优势 :
- 避免全局样式污染
- 组件样式隔离,提高可维护性
- 无需担心类名冲突
六、Vue 与传统开发的哲学对比
范式转变
| 传统开发 | Vue 开发 |
|---|---|
| 命令式编程:告诉计算机如何一步步做某事 | 声明式编程:告诉计算机你想要什么结果 |
操作 DOM 元素:element.textContent = 'Hello' |
操作数据:message = 'Hello' |
| 手动管理状态与视图同步 | 响应式系统自动同步 |
| 事件驱动:点击按钮 → 更新数据 → 更新视图 | 数据驱动:更新数据 → 自动更新视图 |
| 代码组织按功能划分 | 代码组织按特性/逻辑划分 |
响应式编程的核心思想
- 数据是源头:视图是数据状态的反映
- 声明依赖关系:告诉框架哪些数据影响哪些视图
- 自动更新:当数据变化时,框架负责更新相关视图
- 细粒度追踪:只更新必要的部分,而非整个页面
结语:从"操纵绳子"到"指挥木偶师"
传统 DOM 操作就像一个木偶表演者,需要同时控制木偶的每一根绳子,手忙脚乱且容易出错。而 Vue3 则让你成为木偶师的指挥者------你只需告诉木偶师你想要什么表演(描述数据状态),木偶师(Vue 响应式系统)会自动协调所有绳子,完美呈现你的意图。
App.vue 中短短几行代码,完成了传统 JavaScript 需要数十行才能实现的功能。这不是魔法,而是工程智慧的结晶------将开发者从繁琐的 DOM 操作中解放出来,专注于业务逻辑和用户体验。
Vue 的真谛:不再思考"如何更新界面",而是思考"数据应该如何变化"。
当你真正理解并拥抱这一理念,前端开发将变得前所未有的简单、高效和愉快。而这,正是 Vue3 Composition API 带给我们的革命性变化。