从命令式 DOM 操作到声明式响应式开发:Vue 3 如何重构你的前端思维

52 阅读6分钟

在前端开发的演进过程中,我们经历了从原生 JavaScript 手动操作 DOM 到现代框架如 Vue 的声明式、响应式编程范式的巨大转变。本文将通过一个简单的“任务清单(Todos)”应用,对比传统 JS 开发思路与 Vue 3 Composition API 的开发方式,深入剖析这种思维模式的升级,并解释其背后的核心理念与性能优势。


一、传统开发:命令式 DOM 操作的痛点

在没有框架的时代,我们通常这样写代码:

<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
})
</script>

这段代码看似简单,但隐藏着几个关键问题:

  1. 命令式操作:我们必须手动查找 DOM 元素(getElementById),再通过 innerHTML 修改内容。这是一种“告诉浏览器每一步该做什么”的机械式编程。
  2. 性能隐患:每次赋值 app.innerHTML = todo 都会触发重排(reflow)和重绘(repaint) 。重排涉及布局计算,代价高昂;即使只是文本更新,也可能导致整个子树重建。
  3. 状态与 UI 耦合:UI 的更新逻辑直接写在事件回调中,难以复用、测试或维护。当功能复杂时(如增删改查、筛选、全选等),代码迅速变得混乱。

“从 JS(V8引擎 快) -> HTML(渲染引擎 慢) 查找”,这种跨引擎通信本身就是性能瓶颈。


二、Vue 的核心思想:描述 UI 与状态的关系

Vue 的出现,彻底改变了这一局面。它让我们从“如何操作 DOM”转向“UI 应该是什么样子”,即声明式编程

在 Vue 3 中,我们不再关心 DOM 操作细节,而是聚焦于数据本身的变化。框架会自动追踪依赖,并高效地更新视图。

以下是一个完整的 Vue 3 版 Todos 组件(使用 <script setup> 语法):

<template>
  <div>
    <!-- 数据绑定 -->
    <h2>{{ title }}</h2>
    <!-- v-model 双向数据绑定 -->
     <!-- @ 是 v-on: 的缩写 用于绑定事件  不用addEventListener -->
      <!-- @event-name.enter  监听键盘输入 当按下回车的时候触发addTodo函数执行 -->
    <input type="text" v-model="title" @keydown.enter="addTodo">

    <!-- v-if 条件渲染指令 只有当有数据时 再去循环遍历 -->
    <ul v-if="todos.length">
      <!-- :key 循环的标配  需要给它key 作为唯一属性 -->
      <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox" v-model="todo.done">
        <!-- : 是v-bind: 的缩写  表示 :里面是JS表达式-->
        <span :class="{done: todo.done}">{{ todo.title }}</span>
        <!-- :class 动态绑定class 通过todo.done 控制类名 是动态的 当todo.done为true 则添加done类 当todo.done为false 则不添加该类 -->
      </li>
    </ul>
    <!-- 没有数据 -->
    <div v-else>
      暂无计划
    </div>

    <div>

      全选<input type="checkbox" v-model="allDone">
      <!-- v-model='allDone' 实际调用set(val) -->

      <!-- {{ 数据绑定 也可以是表达式的结果绑定 }}  计算之后的数据绑定  -->
      <!-- 未完成任务的长度(个数) -->
      <!-- {{ todos.filter(todo => !todo.done).length }}
      /
      {{ todos.length }} -->
      <!-- 总共的长度 -->

      {{ active }}
      /
      {{ todos.length }}

      <!-- 哪个更优秀?   computed -->

    </div>

  </div>
</template>


<script setup>//setup  vue3 composition 组合式API  如果不是setup  vue2 options API
// 业务是页面上要动态展示标题 且支持编辑标题
// vue 让我们能够focus于标题数据业务  当修改数据时 余下的DOM更新 Vue替我们做了

import { ref, computed } from 'vue'
//响应式数据
const title = ref('')
const todos = ref([

  {
    id: 1,
    title: '打王者',
    done: false
  },
  {
    id: 2,
    title: '吃饭',
    done: true
  },


])

const addTodo = () => {
  // focus 数据业务
  if(!title.value) return
  todos.value.push({
    id: Math.random(),
    title: title.value,
    done: false
  })
  //添加过后 清空表单
  title.value = ''
}

//依赖于todos响应式数据的 计算属性
//形式上是函数(计算过程) 计算结果(计算属性)返回
//也是响应式的  依赖于todos
const active = computed(() => {
  return todos.value.filter(todo => !todo.done).length
})


//全选
// computed 高级技巧
// get set 属性的概念
const allDone = computed({
  get(){
    //判断是否全选 (所有的done === true)
    return todos.value.every(todo => todo.done)
  },
  set(val){
    //批量设置所有todo.done 当用户点击'全选'复选框时 val设置为true 或 false   勾选 或 取消勾选
    todos.value.forEach(todo => todo.done = val)
  }
})



</script>

<style>
  /* 在样式部分设置一横杠 */
  .done{
    text-decoration: line-through;
  }
</style>

三、Vue 开发范式的五大优势

1. 响应式数据驱动

通过 ref() 创建响应式变量,任何对其 .value 的修改都会自动触发视图更新。开发者只需关注“数据怎么变”,无需手动操作 DOM。

“Vue 让我们能够 focus 于标题数据业务,当修改数据时,余下的 DOM 更新 Vue 替我们做了。”

2. 指令系统简化逻辑

  • v-model:实现双向绑定,省去 addEventListener 和手动同步。
  • v-for + :key:高效列表渲染,key 帮助 Vue 识别节点身份,最小化 DOM 操作。
  • v-if / v-else:条件渲染,逻辑清晰。
  • @keydown.enter:事件修饰符,简洁监听特定键盘事件。

这些指令让模板语义更明确,代码可读性大幅提升。

3. 计算属性(computed)的性能优化

对比两种写法:

<!-- ❌ 直接在模板中写表达式 -->
{{ todos.filter(todo => !todo.done).length }}

<!-- ✅ 使用 computed -->
{{ active }}

前者每次组件重新渲染(哪怕与 todos 无关的数据变化)都会重新执行过滤,因为它就是一个计算、一个表达式,它不会拒绝更新;而 computed 具备缓存机制,只有todos变化时才会重新计算:

“内部维护一个 _dirty 标志,当依赖的响应式数据变化时标记为 dirty,下次读取时如果是 dirty 则重新计算并缓存结果,否则直接返回缓存值。”

这避免了不必要的重复计算,显著提升性能。

4. 高级 computed:getter/setter 实现复杂逻辑

全选功能通过 computedget/set 实现:

const allDone = computed({
  get() {
    return todos.value.every(todo => todo.done)
  },
  set(val) {
    todos.value.forEach(todo => todo.done = val)
  }
})

当用户点击“全选”复选框时,v-model="allDone" 会调用 set 方法,批量更新所有任务状态。这种模式将 UI 行为与数据逻辑完美解耦。

5. 虚拟 DOM 与高效更新

虽然开发者不直接接触 DOM,但 Vue 底层通过虚拟 DOM(VNode) 实现高性能更新:

“Vue 将模板编译为 render 函数,生成 VNode(虚拟节点)。更新时,对比新旧 VNode 树,最小化真实 DOM 操作。”

这意味着即使你写了看似“暴力”的数据变更(如替换整个数组),Vue 也能智能 diff,只更新真正变化的部分。


四、思维转变:从“操作 DOM”到“管理状态”

传统开发的核心是 “找到元素 → 修改它” ,而 Vue 的核心是 “定义状态 → 描述 UI 如何随状态变化”

这种转变带来三大好处:

  1. 降低心智负担:新手无需理解 DOM 树、事件冒泡、重排重绘等底层概念,也能写出高质量代码。
  2. 提升可维护性:数据流清晰,逻辑集中,便于调试和扩展。
  3. 天然支持组件化:每个组件管理自己的状态和 UI,组合成复杂应用。

Vue 开发方式 简单 高效 好上手”。


结语

从原生 JS 到 Vue 3,不仅是工具的升级,更是编程范式的跃迁。我们不再被 DOM 操作束缚,而是站在更高维度思考应用的状态与交互。Vue 的响应式系统、指令语法、计算属性和虚拟 DOM 共同构建了一个高效、直观、可维护的开发体验。

对于初学者,Vue 让你快速上手;对于资深开发者,它提供了足够的灵活性与性能保障。在这个“数据驱动 UI”的时代,掌握 Vue 的声明式思维,就是掌握了现代前端开发的核心能力。