目录
一、v-model
核心作用: v-model
的主要功能是在表单输入元素 或自定义 Vue 组件 上创建双向数据绑定。它同时负责数据的读取(将 Vue 数据绑定到视图)和数据的写入(将用户输入同步回 Vue 数据)。
在原生表单元素上的实现原理:
html
<!-- HTML 模板 -->
<input v-model="username">
等效于以下写法:
html
<input
:value="username"
@input="username = $event.target.value"
>
在自定义组件上的实现原理:
html
<custom-input v-model="username">
等效于以下写法:
html
<custom-input
:model-value="username"
@update:model-value="username = $event"
>
组件内部实现:
javascript
// 自定义组件内部
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
handleInput(e) {
this.$emit('update:modelValue', e.target.value)
}
}
二、v-bind
核心作用: v-bind
的核心功能是动态地将 Vue 实例(组件)中的数据绑定到 HTML 元素的属性(Attribute)或组件的属性(Prop)上。
:model 是 Vue 的动态属性绑定语法,由两部分组成:
- 冒号' : '它是' v-bind:'的缩写
- 属性名 model:任意自定义的属性名
- 完整形式:v-bind:model="dataObj"
:model 不是一个内置指令或特殊属性,而是一种常见的命名约定,表示"传递整个模型对象"。
:model
的本质
- 是
v-bind:model
的简写形式 - 仅用于单向数据传递(父 → 子)
- 无内置行为,是纯属性绑定
- 依赖开发者明确定义props接收
v-model 和 :model 的区别
特性 | v-model | :model |
---|---|---|
类型 | Vue指令 | 动态属性绑定 |
功能 | 双向数据绑定 | 单向数据绑定 |
实现 | 属性+事件绑定 | 仅属性绑定 |
用途 | 表单输入组件 | 传递任意对象 |
示例 | <input v-model="val"> |
<child :model="dataObj"> |
组件实现 | 需要emit更新事件 | 仅接收props |
适用场景选择
场景 | 推荐 |
---|---|
表单输入绑定 | v-model |
复杂数据传递 | :model |
组件通信 | 组合使用 :model + 自定义事件 |
多个数据绑定 | 多个 v-model (如 v-model:user ) |
三、computed(计算属性)
**核心作用:**
创建响应式的派生数据,基于其他响应式数据自动计算结果并缓存
javascript
import { computed } from 'vue'
// 基本用法
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// 可写计算属性
const writableComputed = computed({
get() { return modelValue.value },
set(newVal) { modelValue.value = newVal }
})
let visibleDialog = computed({
get() {
return props.visible;
},
set() {
closeDialog();
},
});
原理深度解析
- 惰性求值:仅当依赖项变化时才重新计算(首次访问时也会计算)
- 依赖追踪:运行时自动追踪getter函数内访问的响应式数据
- 缓存机制:依赖项未变化时直接返回缓存结果(多次访问不重复计算)
- 响应式传播:计算属性本身就是响应式ref,改变会自动触发依赖更新
- 更新时机:在Vue的响应式更新周期中同步执行
经典使用场景
- 格式化/组合数据(如
${firstName} ${lastName}
) - 过滤/转换数据(如
todos.filter(t => !t.done)
) - 复杂数学运算(如购物车总价计算)
- 从props派生组件内部状态(需可写计算属性)
- 替代模板中复杂表达式(提升可读性)
computed 的自动依赖追踪原理:
computed 会监听内部所有依赖的响应式变量值变化,自动触发重新计算并缓存结果。
javascript
const tipsVisible1 = computed(() => repulseForm.backToModifyType == 1);
详细过程
依赖收集阶段:
- 当首次访问 computed 属性时,Vue 会执行计算函数
- 计算函数内部访问的每一个响应式变量(如
repulseForm.backToModifyType
) 都会通过"发布-订阅"机制将这个计算函数标记为自身的一个依赖
响应更新阶段:
- 当这些被依赖的响应式变量值改变时:
- 所有依赖它的计算函数会自动触发重新计算
- 结果会被缓存,如果依赖没有变化则直接返回缓存值
缓存机制:
- 当多次访问
chooseNodeVisible1
时: - 只有当
repulseForm.backToModifyType
改变时才会重新计算 - 否则直接返回上次计算结果
示例:
javascript
let visibleDialog = computed({
get() {
return props.visible;
},
set() {
closeDialog();
},
});
触发 getter:
- 首次访问时
- 依赖项 (
props.visible
) 变化后再次访问时 - 每次在模板中渲染时
触发 setter:
- 任何赋值操作 (
visibleDialog.value = ...
) - 即使赋予的值与当前值相同
- 通过 v-model 绑定时被调用
四、watch(侦听器)
**核心作用:**
显式侦听特定数据源 ,在变化时执行副作用(异步请求、DOM操作等)
javascript
import { watch } from 'vue'
// 监听单一源
watch(count, (newVal, oldVal) => {
console.log(`计数变化: ${oldVal} → ${newVal}`)
})
// 监听多个源
watch([firstName, lastName], ([newFirst, newLast]) => {
// 处理变化...
})
// 深度监听对象
watch(
() => ({ ...user }),
(newUser) => { /* 处理 */ },
{ deep: true, immediate: true }
)
原理深度解析
- 精确依赖:必须显式指定监听的数据源(ref、reactive、getter函数)
- 变化检测 :默认浅层比较(对象需手动开启
deep:true
) - 回调机制:提供新旧值对比能力
- 异步执行 :默认在组件更新后 执行(通过
flush
选项可调整时机) - 资源回收:自动绑定组件生命周期(卸载时自动取消监听)
核心配置选项
选项 | 作用 | 典型场景 |
---|---|---|
deep |
深度监听嵌套对象变化 | 表单对象监听 |
immediate |
立即执行回调 | 初始数据加载 |
flush |
控制回调执行时机 | DOM操作时使用'post' |
经典使用场景
- 异步操作、副作用操作
- API数据获取(搜索词变化时发起请求)
- 路由参数监听(
$route.params.id
变化时加载数据) - 表单验证(字段变化时触发验证)
- 操作历史记录(状态变化时记录操作)
深度监听:
javascript
watch(
// 源获取函数:只监听user.profile对象
() => user.profile,
// 变化回调
(newProfile) => {
console.log("用户资料更新:", newProfile);
// 执行相关操作
},
// 配置选项
{ deep: true } // 启用深度监听
);
javascript
// 以下操作都会触发监听:
user.profile.name = "张三"; // 修改属性
user.profile.address.city = "北京"; // 修改嵌套属性
user.profile.hobbies.push("阅读"); // 修改数组内容
javascript
// 如果需要新旧值对比,应创建副本:
watch(
() => ({ ...user.profile }), // 创建浅拷贝
(newProfile, oldProfile) => {
// 现在可以比较具体变化
},
{ deep: true }
);
javascript
// watchEffect精确控制
watchEffect(() => {
// 只追踪需要的具体属性
if (needTracking) {
const importantValue = someObj.deep.nested.value;
// 使用importantValue...
}
});
五、watchEffect(即时副作用)
**核心作用:**
自动追踪依赖 并立即执行副作用,响应式数据变化时自动重新运行
javascript
import { watchEffect } from 'vue'
// 基本用法
const stop = watchEffect(() => {
document.title = `消息(${unreadCount.value})`
})
// 清理副作用
watchEffect((onCleanup) => {
const timer = setTimeout(() => {...}, 1000)
onCleanup(() => clearTimeout(timer))
})
// 控制执行时机
watchEffect(() => {...}, { flush: 'post' })
原理深度解析
- 自动依赖收集:运行时自动追踪函数内访问的所有响应式数据
- 立即执行 :首次调用时同步执行(与
watch
的immediate模式不同) - 智能清理 :通过
onCleanup
注册清理函数(下次执行前调用) - 动态依赖:每次执行重新收集依赖(条件分支变化时自动调整)
- 执行控制 :默认在组件更新前 执行(DOM操作用
flush: 'post'
)
独特优势
- 避免手动维护依赖列表
- 处理动态依赖(条件分支中的响应式数据)
- 更符合命令式编程思维
- 简化初始化逻辑(自动执行)
经典使用场景
- 实时更新DOM(如标题、滚动位置)
- 自动请求取消(
onCleanup
中取消请求) - 监听鼠标/键盘事件(自动取消注册)
- 表单自动保存(输入变化后定时保存)
- 日志记录(自动跟踪变化数据)
三者的对比决策表
特性 | computed |
watch |
watchEffect |
---|---|---|---|
返回类型 | 响应式ref | 无 | 无 |
执行时机 | 需要时计算 | 变化后回调 | 立即+变化时 |
依赖声明 | 自动 | 显式声明 | 自动 |
新旧值 | 无 | 提供 | 无 |
缓存机制 | ✅ | ❌ | ❌ |
异步支持 | ❌(纯函数) | ✅ | ✅ |
清理机制 | ❌ | ❌ | ✅(onCleanup) |
典型用途 | 派生数据 | 响应变化 | 执行副作用 |
**如何选择?**
- 需要推导新数据 吗? →
computed
- 需要获取变化前后值 吗? →
watch
- 操作包含异步/副作用 吗?
- 依赖明确 →
watch
- 依赖动态 →
watchEffect
- 依赖明确 →
- 需要自动执行初始化 ? →
watchEffect
- 涉及资源清理 ? →
watchEffect(onCleanup)
最佳实践 :优先使用
computed
派生数据,用watchEffect
处理副作用,只在需要精确控制时使用watch
总结:
computed
- 自动追踪依赖关系
- 懒计算(在访问时执行)
- 结果缓存(依赖未变则返回缓存)
- 适用于模板绑定、值派生、多依赖计算
watch
- 显示声明监听目标
- 即时响应数据变化
- 支持异步操作
- 适用于数据联动、异步操作、复杂副作用处理