🧠 一、核心思想:从“操作 DOM”到“操作数据”
❌ 传统做法(命令式)
js
编辑
// 找到输入框、按钮、列表
const input = document.querySelector('#input');
const btn = document.querySelector('#add-btn');
const list = document.querySelector('#todo-list');
btn.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = input.value;
list.appendChild(li);
input.value = '';
});
- 问题:代码耦合度高,逻辑分散,难以维护。
- 思维模式:先找元素 → 再改内容 → 手动同步状态。
✅ Vue 做法(声明式 + 响应式)
“你只管改数据,DOM 更新我来搞定。”
在 Vue 中,我们不再关心“如何更新页面”,而是专注思考:
- 数据结构是什么?
- 用户交互会如何改变数据?
框架自动追踪依赖,在数据变化时高效更新视图。
🧩 二、代码逐层解析
1. 模板部分(Template)
vue
编辑
<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>
🔑 关键指令说明:
| 指令 | 作用 | 简写/说明 |
|---|---|---|
{{ }} | 插值表达式 | 动态显示数据 |
v-model | 双向数据绑定 | 自动同步 input 与 data |
@keydown.enter | 事件监听(回车) | @ 是 v-on: 的缩写 |
v-if / v-else | 条件渲染 | 控制元素是否渲染 |
v-for | 列表渲染 | 遍历数组生成元素 |
:key | 唯一标识 | 提升 diff 效率,必加! |
:class | 动态绑定 class | : 是 v-bind: 的缩写 |
💡
:class="{ done: todo.done }"
当todo.done === true时,<span>会加上done类,触发 CSS 样式(灰色+删除线)。
🔍 三、深度辨析:v-bind(:) vs v-on(@)
你提到的「:」其实是 Vue 中 v-bind 指令的简写,而 @ 是 v-on 指令的简写。虽然都叫“绑定”,但二者绑定的目标、作用、场景完全不同。
下面从多个维度详细对比,帮你彻底区分:
一、核心定位对比
| 维度 | v-bind(简写 :) | v-on(简写 @) |
|---|---|---|
| 绑定目标 | DOM 属性(如 src/class/style)、组件 props、自定义属性 | DOM 事件(如 click/input)、组件自定义事件 |
| 作用 | 「数据 → 视图」:将 Vue 数据同步到 DOM / 组件 | 「视图 → 数据」:监听视图事件,触发逻辑修改数据 |
| 本质 | 设置元素 / 组件的「状态 / 属性」 | 给元素 / 组件添加「事件监听器」 |
| 取值类型 | 表达式(变量、计算属性、对象、数组等) | 方法名、内联表达式、事件处理函数 |
二、具体用法对比
1. v-bind(:):绑定「属性 / Props」
核心是 把 Vue 实例的数据,赋值给 DOM 元素的属性或组件的 props。
✅ 示例 1:绑定原生 DOM 属性
vue
编辑
<!-- 完整写法 -->
<img v-bind:src="imgUrl" alt="">
<!-- 简写 -->
<img :src="imgUrl" alt="">
<!-- 绑定 class/style(支持对象/数组语法) -->
<div :class="{ active: isActive }" :style="{ color: textColor }"></div>
<!-- 绑定自定义属性(Vue 3 中可直接用 :data-id) -->
<div :data-id="itemId"></div>
imgUrl、isActive、textColor是 Vue 响应式数据;- 数据变化时,DOM 属性自动更新(响应式同步)。
✅ 示例 2:绑定组件 Props
vue
编辑
<!-- 父组件:向子组件传递数据 -->
<Child :name="username" :age="userAge"></Child>
<!-- 子组件:接收 props -->
<script setup>
defineProps(['name', 'age'])
</script>
v-bind是父→子通信的唯一标准方式(Props 单向数据流)。
2. v-on(@):绑定「事件」
核心是 给 DOM 元素 / 组件绑定事件监听器,事件触发时执行指定逻辑。
✅ 示例 1:绑定原生 DOM 事件
vue
编辑
<!-- 完整写法 -->
<button v-on:click="handleClick">点击</button>
<!-- 简写 -->
<button @click="handleClick">点击</button>
<!-- 键盘事件 -->
<input @keyup.enter="handleSearch">
handleClick、handleSearch是方法;- 用户交互(点击、回车)触发后,可修改数据,进而驱动视图更新。
✅ 示例 2:绑定组件自定义事件
vue
编辑
<!-- 子组件:触发事件 -->
<button @click="$emit('confirm', '子组件数据')">确认</button>
<!-- 父组件:监听事件 -->
<Child @confirm="handleChildConfirm"></Child>
v-on是实现子→父通信的关键机制。
三、关键区别:「数据绑定」vs「事件绑定」
| 对比项 | v-bind(:) | v-on(@) |
|---|---|---|
| 数据流向 | 数据 → 视图(单向) | 视图 → 数据(通过事件回调) |
| 触发时机 | 初始化 + 数据变化时自动执行 | 事件发生时被动触发 |
| 典型修饰符 | .prop、.camel(Vue 2 还有 .sync) | .stop、.prevent、.once、.enter 等 |
🌟 特别注意:
:value="inputVal"不会自动同步用户输入回inputVal;
而@input="inputVal = $event.target.value"才能实现反向同步。
四、易混场景:v-model(结合了两者)
v-model 本质是 语法糖,内部同时使用了 v-bind 和 v-on:
vue
编辑
<!-- 简写 -->
<input v-model="inputVal">
<!-- 等价于 -->
<input
:value="inputVal"
@input="inputVal = $event.target.value"
>
这完美体现了:
:(v-bind)负责 把数据给视图(设置value);@(v-on)负责 把视图的变化同步回数据(监听input事件)。
五、一句话总结使用场景
| 场景 | 用 v-bind(:) | 用 v-on(@) |
|---|---|---|
给 <img> 绑定 src | ✅ | ❌ |
给 <div> 绑定 class/style | ✅ | ❌ |
| 父组件给子组件传值 | ✅ | ❌ |
| 点击按钮执行逻辑 | ❌ | ✅ |
| 监听输入框内容变化 | ❌ | ✅ |
| 父组件监听子组件事件 | ❌ | ✅ |
✅ 口诀:
**冒号(:)绑属性,@ 绑事件;
是数据到视图,@ 是视图触发数据改。**
🔥 四、深度解析:“全选”功能是如何实现的?
要彻底搞懂全选功能,我们必须理解 可读写计算属性 allDone 与 复选框 v-model 的联动机制。下面从三个维度拆解:
一、先看核心代码(回顾)
html
预览
<!-- 模板:全选复选框 -->
全选 <input type="checkbox" v-model="allDone">
js
编辑
// 脚本:可读写计算属性 allDone
const allDone = computed({
get() {
return todos.value.every(todo => todo.done);
},
set(value) {
todos.value.forEach(todo => todo.done = value);
}
});
二、全勾选的实现逻辑(分 2 种场景)
场景 1️⃣:手动勾选 / 取消 “全选框” → 所有待办被勾选 / 取消
这是正向联动,由用户主动操作全选框触发:
-
用户点击“全选”复选框 → 浏览器将
checked设为true或false; -
v-model="allDone"将这个布尔值赋值给allDone;- 底层:
v-model展开为:checked="allDone"+@change="allDone = $event.target.checked" - 其中
:checked是v-bind,@change是v-on
- 底层:
-
由于
allDone是可写的计算属性,赋值操作触发set(value)方法; -
set方法遍历todos,将每个todo.done设置为value; -
每个待办项的复选框通过
v-model="todo.done"(即:checked + @change)绑定,因此会自动同步状态。
✅ 本质:
v-on触发set→ 批量修改数据 →v-bind更新子项视图。
场景 2️⃣:手动勾选完所有单个待办 → 全选框自动勾选
这是反向联动,由子项变化驱动父控件:
-
用户逐个勾选待办项 → 对应的
todo.done变为true; -
todos是ref响应式数据,其内部变化会被 Vue 追踪; -
allDone的get()方法依赖于todos,因此会自动重新执行; -
get()使用Array.prototype.every()检查:是否所有todo.done === true?every():只有全部满足条件才返回true,否则false;
-
若返回
true,则allDone的值变为true; -
全选框的
:checked="allDone"(来自v-model)读取该值 → 自动勾选。
✅ 本质:子项数据变 → 触发
get重算 →v-bind更新全选框状态。
三、核心关键点总结
| 核心环节 | 作用 |
|---|---|
v-model="allDone"(全选框) | 展开为 :checked="allDone"(v-bind) + @change="..."(v-on) |
computed 的 get() | 实时检查所有待办是否完成,返回“是否全选”的布尔值 |
computed 的 set(value) | 接收全选框的新状态,批量设置所有 todo.done = value |
v-model="todo.done"(单个框) | 同步单个待办的勾选状态 ↔ 数据字段 |
响应式数据(ref) | 确保 todos 和 allDone 的变化能触发视图更新 |
四、举个具体例子(更容易理解)
假设初始状态:
js
编辑
todos.value = [
{ id:1, title:'打王者', done:true },
{ id:2, title:'吃饭', done:true }
];
- 页面加载时:
allDone.get()执行 →every()返回true→ 全选框通过:checked="true"勾选 ✅ - 你取消“打王者” :
todo.done = false→todos变化 →allDone.get()重算 → 返回false→ 全选框通过:checked="false"取消 ❌ - 你点击全选框勾选:
@change触发 →allDone = true→ 触发set(true)→ 所有todo.done = true→ 子项复选框通过:checked更新 ✅
整个过程完全由数据驱动,且清晰分离了 属性绑定(:) 与 事件绑定(@) 的职责。
五、为什么不用普通变量 / 方法实现?
如果放弃 computed 的可读写特性,改用普通方法,代码会变得冗长且难以维护:
js
编辑
// ❌ 普通方式(繁琐)
const allDone = ref(false);
watch(todos, () => {
allDone.value = todos.value.every(todo => todo.done);
});
const toggleAll = (value) => {
todos.value.forEach(todo => todo.done = value);
};
而使用 可读写 computed:
- 自动响应依赖变化(无需
watch) - 读写逻辑封装一体(
get/set) - 模板直接
v-model绑定(简洁直观)
✅ 这就是 Vue 计算属性的高级威力:把复杂的状态映射逻辑,变成一个“看起来像普通变量”的响应式值。
📌 五、总结要点
| 概念 | 说明 |
|---|---|
| 响应式核心 | 数据变 → 视图自动更新,开发者专注数据流 |
| Composition API | ref + computed + 函数组合,逻辑更清晰 |
v-bind(:) | 绑定属性/props,实现 数据 → 视图 |
v-on(@) | 绑定事件,实现 视图 → 数据 |
v-model | 是 v-bind + v-on 的语法糖,实现双向绑定 |
| computed 缓存机制 | 避免重复计算,提升性能 |
| 可读写 computed | 实现“全选”这类双向状态映射的最佳实践 |
| Array.every() | 判断数组是否全部满足条件,是实现“全选检测”的关键 JS 方法 |