🌱 从命令式到响应式:用 Todo 示例读懂 Vue 的开发哲学
在前端的世界里,曾有一段“人肉操作 DOM”的岁月——开发者像园丁一样,手持剪刀,逐叶修剪,生怕一不小心就剪歪了页面的枝桠。 而 Vue 的出现,不是递给你一把更快的剪刀,而是悄悄在土壤里埋下了一套智能灌溉系统:你只需关心种子(数据)是否健康,叶子(界面)自会随之生长。
这,就是从命令式到响应式的跃迁—— 一场让代码回归思考、让开发重获自由的哲学革命。
🌟 一、传统 DOM 操作:体力劳动式的“命令式开发”
先来看一段html,它展示了最典型的 传统 DOM 操作流程:
找到元素 → 监听事件 → 读取值 → 修改 DOM。
<!-- html 示例 -->
<h2 id="app"></h2>
<input type="text" id="todo-input">
<script>
// 找到元素
const app = document.getElementById('app');
const todoInput = document.getElementById('todo-input')
// 给输入框绑定一个 `change` 事件监听器
todoInput.addEventListener('change',function(event){
const todo = event.target.value.trim();// 读取值
if(!todo){
console.log('请输入任务');
return;
}
app.innerHTML = todo;// 修改DOM
})
</script>
这段代码体现了传统 JS 的几个特点:
1. 机械、琐碎、容易出错
你必须手动查询元素、手动更新元素、手动监听事件。就像对机器人逐条发指令,非常低效。
2. 性能隐患:JS 快、DOM 慢
任何一次 innerHTML 都可能引发回流与重绘。
3. 不利于扩展
当页面结构复杂后,你需要维护大量 DOM 代码,堆叠错误也随之出现。
🌿 二、Vue 的哲学:数据驱动界面(响应式开发)
面对如此繁重的手动 DOM 操作,开发者迫切需要一种更高级的抽象——不再关注“如何更新界面”,而是聚焦“界面应该是什么状态”。
Vue 的答案是:用数据驱动视图
✅ Vue 开发的核心思路:数据驱动 + 响应式系统
响应式是什么?
一句话:Vue 不是让你去操作 DOM,而是操作数据,然后 DOM 自动更新。
更形象一点:
你不是直接给植物修剪叶子,而是浇水和施肥,植物自然长成你想要的模样。
Vue 做的,就是“帮你自动修剪”。
下面,我们用一个完整的待办事项(Todo)应用,看看响应式开发如何让代码更简洁、更可靠。
<template>
<div>
<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>
import { ref, computed } from 'vue'
const title = ref('')
const todos = ref([
{ id: 1, title: '睡觉', done: false }
])
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
const addTodo = () => {
if (!title.value) return
todos.value.push({
id: Math.random(),
title: title.value,
done: false
})
title.value = ''
}
const allDone = computed({
get() {
return todos.value.every(todo => todo.done)
},
set(val) {
todos.value.forEach(todo => todo.done = val)
}
})
</script>
🌟 点击预览:在线运行这个响应式 Todo 应用
别被代码吓到!我们拆解来看。
🧠 三、响应式核心解析
🪴 1. ref:给数据装上“感应器”
const title = ref('')
这行代码看起来简单,但它做了一件神奇的事:
把普通的变量 ''(空字符串)变成了一个“会说话的数据” 。
- 在普通 JavaScript 里,你改一个变量,页面不会知道。
- 但在 Vue 里,用
ref()包起来的数据,就像装了“感应器”——只要它变了,用到它的地方就会自动更新。
比如 <h2>{{ title }}</h2> 这行模板,意思是:“这里要显示 title 的值”。
当你在输入框里打字时,title 会变,Vue 就立刻把 <h2> 里的文字也改掉——你不用写一行 DOM 操作代码!
✅ 小结:
ref()让数据变得“有感知力”,是响应式系统的起点。
🔁 2. v-model:输入框和数据的“双胞胎绑定”
<input v-model="title" @keydown.enter="addTodo">
这行代码实现了输入框和 title 的双向同步,什么意思?
- 当你在输入框里打字 →
title自动变成你输入的内容 - 当你用代码把
title设为空(比如添加任务后)→ 输入框自动清空
在传统写法里,这需要:
// 手动监听输入
input.addEventListener('input', (e) => {
title = e.target.value // 手动赋值
})
// 手动清空
input.value = ''
而 v-model="title" 一句话就搞定了!
它就像给输入框和 title 绑了一根“魔法绳子”——一端动,另一端跟着动。
✅ 小结:
v-model是 Vue 提供的“双向绑定快捷方式”,专门用于表单元素(输入框、复选框等)。
➕ 3. addTodo:添加任务的“小管家”
const addTodo = () => {
if (!title.value) return
todos.value.push({
id: Math.random(),
title: title.value,
done: false
})
title.value = ''
}
这个函数看起来简单,但它完成了三个关键动作,就像一个贴心的小管家:
✅ 第一步:检查有没有输入内容
if (!title.value) return
- 如果输入框是空的(比如用户按了回车但没打字),就立刻退出,不干任何事。
- 这避免了往列表里添加空白任务。
💡 注意:这里读取的是
title.value,因为title是用ref()创建的,它的实际值藏在.value里面(这是 Vue 3 的一个小规则,初学者记住就行)。
✅ 第二步:把新任务加到列表里
todos.value.push({ ... })
-
创建一个新任务对象,包含:
id:用Math.random()生成一个临时唯一编号(真实项目会用更可靠的方式)title:当前输入框的内容done: false:默认是“未完成”
-
然后把它放进
todos数组里
🌟 重点来了:因为
todos是用ref()创建的响应式数据,一旦调用push()添加新项,Vue 会立刻发现变化,并自动更新页面上的<li>列表!
你不需要手动写innerHTML或appendChild——这就是响应式的威力。
✅ 第三步:清空输入框
title.value = ''
- 把
title设为空字符串 - 由于输入框通过
v-model="title"和它绑定,输入框会自动清空,准备接收下一个任务!
🧩 总结:addTodo 的哲学
它没有操作任何 DOM 元素,却完成了:
- 输入校验
- 数据更新
- 界面同步
🧮 4. computed:会自动算数的“聪明变量”
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
这个 active 不是一个普通数字,而是一个会自己重新计算的“聪明变量” 。
- 它的作用是:统计“还有多少任务没完成”
- 关键在于:只要
todos里的任何一项任务状态变了(比如打勾了),active就会自动重新算一遍
你不需要写:
// 手动更新计数(传统写法)
updateCount() {
const count = ...;
document.getElementById('count').innerText = count;
}
Vue 会自动追踪:active 依赖了 todos → todos 变了 → active 重新计算 → 页面上的 {{ active }} 自动更新。
✅ 小结:
computed适合用来定义“基于其他数据计算出来的值”,而且它会自动缓存、自动更新,高效又省心。
🎯 额外彩蛋:全选功能为什么这么简单?
const allDone = computed({
get() { /* 是否全部完成 */ },
set(val) { /* 设置全部完成状态 */ }
})
这个 allDone 更厉害——它不仅能“读”,还能“写”!
- 读(get) :当页面要显示全选框是否勾选时,Vue 问它:“现在是不是全完成了?” → 它检查所有任务,返回
true或false - 写(set) :当你点击全选框时,Vue 告诉它:“用户想设成 XXX 状态” → 它就去把所有任务的
done都改成那个值
一个变量,搞定双向逻辑!
传统写法可能要分别监听全选框和每个子任务,再写一堆同步代码,极易出错。而 Vue 让你用“声明式”的方式说清楚规则,系统自动执行。
🌟 总结: 两种思维,两种世界
在命令式开发中,你像一名工头,事无巨细地指挥浏览器:“找到这个元素,改它的内容,监听那个事件,更新另一块区域”——界面是你一行行命令堆砌的结果。
而在响应式开发中,你像一位园丁,只负责照料数据的土壤:任务是什么、哪些已完成、总数多少。界面不再是你要操控的对象,而是数据状态自然生长的模样。
前者关注“怎么做”,后者只定义“是什么”。
正是这一转变,让前端开发从繁琐的 DOM 操作中解放出来,回归到对状态与逻辑的思考——这才是 Vue 带给我们的真正自由。
关注“是什么”,而不是“怎么做”。