从 DOM 操作到响应式驱动:一个 Todo 应用带你理解 Vue 的强大之处

62 阅读4分钟

在学习现代前端框架时,很多人会问:“为什么 Vue/React 这样写代码?它们到底比原生 JS 强在哪里?”

今天,我们通过一个简单的 Todo 列表应用,对比传统 DOM 操作与 Vue 响应式开发的差异,深入理解“响应式数据驱动界面”这一核心思想。

你将看到:

  • 为什么不再需要手动操作 DOM?
  • refv-modelcomputed 是如何工作的?
  • Vue 如何让你聚焦业务逻辑,而非 DOM 操作

一、传统方式:手动操作 DOM —— 繁琐且易错

先看一段最原始的 JavaScript 代码实现一个输入框更新标题的功能:

<h2 id="app"></h2>
<input type="text" id="todo-input">

<script>
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; // 手动修改 DOM
});
</script>

问题分析

  1. 必须先找到 DOM 元素getElementById 是第一步;
  2. 事件监听复杂:需要绑定事件,处理空值等边界情况;
  3. DOM 操作低效:每次修改都要重新设置 innerHTML,性能差;
  4. 代码耦合严重:UI 与逻辑紧密绑定,难以维护和复用。

这就是“命令式编程”的典型代表:我告诉你怎么做(操作 DOM),而不是你想表达什么(数据变化)


二、Vue 方式:响应式驱动 —— 数据是核心

现在我们用 Vue 3 实现同一个功能,但体验完全不同。

完整代码示例

<template>
  <div>
    <h1></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>
import { ref, computed } from 'vue';

const title = ref('');
const todos = ref([
  { id: 1, title: '打王者', done: false },
  { id: 2, title: '吃饭', done: true },
  { id: 3, title: '睡觉', done: false },
  { id: 4, title: '学习Vue', done: false }
]);

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(val) {
    todos.value.forEach(todo => todo.done = val);
  }
});
</script>

<style>
.done {
  color: gray;
  text-decoration: line-through;
}
</style>

三、关键概念解析:Vue 的强大之处

1. ref:自动追踪数据变化

const title = ref('');
const todos = ref([...]);
  • ref 将普通变量包装为响应式对象
  • title.valuetodos.value 被修改时,Vue 会自动触发视图更新;
  • 不再需要手动调用 innerHTMLappendChild

优势:开发者只需关注数据变化,Vue 自动完成 UI 同步。


2. v-model:双向绑定简化交互

<input type="text" v-model="title">

等价于:

input.addEventListener('input', e => {
  title.value = e.target.value;
});

v-model 更简洁、更安全、更高效。

优势

  • 减少样板代码;
  • 避免手动事件绑定出错;
  • 支持多种输入类型(checkbox、radio、select 等)。

3. v-for:数据驱动列表渲染

<li v-for="todo in todos" :key="todo.id">
  • 根据 todos 数组自动生成列表;
  • 添加或删除项时,自动更新 DOM;
  • :key 提供唯一标识,优化 diff 算法性能。

优势

  • 不再需要手动创建元素;
  • 避免内存泄漏和重复节点;
  • 性能优于手动循环插入。

4. computed:智能计算属性

const active = computed(() => {
  return todos.value.filter(todo => !todo.done).length;
});
  • 计算结果是响应式的
  • 只有当依赖的数据变化时才会重新执行;
  • 内部缓存机制避免重复计算,提升性能。

优势

  • 代码可读性高;
  • 自动更新,无需手动触发;
  • 支持 get/set 实现复杂逻辑(如全选控制)。

四、Vue 相比原生 JS 的六大核心优势

对比维度原生 JSVue
开发效率需要手动查找节点、绑定事件、操作 DOM使用指令(v-model, v-for)快速构建界面
代码质量易出错,难以维护结构清晰,逻辑分离
性能表现频繁操作 DOM,性能差虚拟 DOM + 差异算法,高效更新
开发体验机械式编码,容易疲劳声明式编程,专注业务逻辑
可扩展性复用困难,组件化成本高组件化设计,易于复用和维护
调试能力错误定位难,状态不可控DevTools 支持,状态可视化

五、结语:Vue 的强大,源于对“数据”的尊重

Vue 并没有发明新的语言或语法,它的强大来自于对 “数据即状态” 这一理念的极致实践。

相比原生 JS,Vue 的优势体现在:

  1. 自动响应:数据变化 → 视图自动更新,无需手动干预;
  2. 声明式编程:你描述“应该是什么样子”,而不是“怎么一步步做”;
  3. 状态管理:所有 UI 都由数据驱动,状态可追踪、可测试;
  4. 生态完善:配合 Vue Router、Pinia、Vite 等工具,形成完整开发体系;
  5. 学习曲线平缓:API 设计友好,适合新手快速上手,又支持高级模式。

一句话总结
Vue 让你从“操作像素”中解脱出来,回归到“建模业务”的本质。

当你不再纠结于 querySelectorcreateElement,而是专注于“用户添加了一个任务”、“待办事项数量变了”这些业务行为时,你就已经站在了现代前端开发的正确轨道上。