从命令式到响应式:用 Todo 示例读懂 Vue 的开发哲学

52 阅读7分钟

🌱 从命令式到响应式:用 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> 列表!
你不需要手动写 innerHTMLappendChild——这就是响应式的威力。

✅ 第三步:清空输入框
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 依赖了 todostodos 变了 → active 重新计算 → 页面上的 {{ active }} 自动更新。

小结computed 适合用来定义“基于其他数据计算出来的值”,而且它会自动缓存、自动更新,高效又省心。


🎯 额外彩蛋:全选功能为什么这么简单?

const allDone = computed({
  get() { /* 是否全部完成 */ },
  set(val) { /* 设置全部完成状态 */ }
})

这个 allDone 更厉害——它不仅能“读”,还能“写”!

  • 读(get) :当页面要显示全选框是否勾选时,Vue 问它:“现在是不是全完成了?” → 它检查所有任务,返回 truefalse
  • 写(set) :当你点击全选框时,Vue 告诉它:“用户想设成 XXX 状态” → 它就去把所有任务的 done 都改成那个值

一个变量,搞定双向逻辑!
传统写法可能要分别监听全选框和每个子任务,再写一堆同步代码,极易出错。而 Vue 让你用“声明式”的方式说清楚规则,系统自动执行。


🌟 总结: 两种思维,两种世界

在命令式开发中,你像一名工头,事无巨细地指挥浏览器:“找到这个元素,改它的内容,监听那个事件,更新另一块区域”——界面是你一行行命令堆砌的结果。

而在响应式开发中,你像一位园丁,只负责照料数据的土壤:任务是什么、哪些已完成、总数多少。界面不再是你要操控的对象,而是数据状态自然生长的模样。

前者关注“怎么做”,后者只定义“是什么”。
正是这一转变,让前端开发从繁琐的 DOM 操作中解放出来,回归到对状态与逻辑的思考——这才是 Vue 带给我们的真正自由。

关注“是什么”,而不是“怎么做”。