从“操纵绳子“到“指挥木偶“:Vue3 Composition API 如何彻底改变前端开发范式

引言

本文将通过一个简单的 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>

传统开发的痛点

  1. 手动 DOM 操作 :每次状态变化都需要手动更新 DOM,getElementByIdcreateElementappendChild 等操作冗长且易错

  2. 状态与视图分离 :数据 (todos) 存储在一个地方,而视图渲染逻辑又在另一个地方,维护一致性困难

  3. 细粒度更新困难:即使只修改一个任务的状态,也需要重新渲染整个列表

  4. 事件处理分散:点击、键盘事件处理逻辑分布在各处,难以追踪

  5. 性能问题:频繁的 DOM 操作会导致页面重排重绘,影响性能

  6. 代码复用性差:逻辑与 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-ifv-else

html 复制代码
<ul v-if="todos.length">
  <!-- 待办事项列表 -->
</ul>
<div v-else>
  暂无计划
</div>
  • 功能:根据表达式值的真假条件性地渲染元素
  • 特点
    • 惰性渲染:初始渲染时条件为假,不会渲染该元素
    • 切换开销高:会销毁和重建内部元素及其绑定的事件监听器
  • v-show 的区别
    • v-show 始终渲染元素,只是通过 CSS display 属性切换
    • 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 开发
命令式编程:告诉计算机如何一步步做某事 声明式编程:告诉计算机你想要什么结果
操作 DOM 元素:element.textContent = 'Hello' 操作数据:message = 'Hello'
手动管理状态与视图同步 响应式系统自动同步
事件驱动:点击按钮 → 更新数据 → 更新视图 数据驱动:更新数据 → 自动更新视图
代码组织按功能划分 代码组织按特性/逻辑划分

响应式编程的核心思想

  1. 数据是源头:视图是数据状态的反映
  2. 声明依赖关系:告诉框架哪些数据影响哪些视图
  3. 自动更新:当数据变化时,框架负责更新相关视图
  4. 细粒度追踪:只更新必要的部分,而非整个页面

结语:从"操纵绳子"到"指挥木偶师"

传统 DOM 操作就像一个木偶表演者,需要同时控制木偶的每一根绳子,手忙脚乱且容易出错。而 Vue3 则让你成为木偶师的指挥者------你只需告诉木偶师你想要什么表演(描述数据状态),木偶师(Vue 响应式系统)会自动协调所有绳子,完美呈现你的意图。

App.vue 中短短几行代码,完成了传统 JavaScript 需要数十行才能实现的功能。这不是魔法,而是工程智慧的结晶------将开发者从繁琐的 DOM 操作中解放出来,专注于业务逻辑和用户体验。

Vue 的真谛:不再思考"如何更新界面",而是思考"数据应该如何变化"。

当你真正理解并拥抱这一理念,前端开发将变得前所未有的简单、高效和愉快。而这,正是 Vue3 Composition API 带给我们的革命性变化。

相关推荐
小裴(碎碎念版)2 小时前
文件读写常用操作
开发语言·爬虫·python
TextIn智能文档云平台2 小时前
图片转文字后怎么输入大模型处理
前端·人工智能·python
专注前端30年2 小时前
在日常开发项目中Vue与React应该如何选择?
前端·vue.js·react.js
sheji34162 小时前
【开题答辩全过程】以 基于Java的应急安全学习平台的设计与实现为例,包含答辩的问题和答案
java·开发语言·学习
winfield8212 小时前
MCP 协议详解
开发语言·网络·qt
文刀竹肃2 小时前
DVWA -XSS(DOM)-通关教程-完结
前端·安全·网络安全·xss
lifejump2 小时前
Pikachu | XSS
前端·xss
进击的野人2 小时前
Vue 组件与原型链:VueComponent 与 Vue 的关系解析
前端·vue.js·面试