Appearance
响应式原理
Vue 的响应式系统是其核心特性之一,它使得数据变化能够自动触发视图更新。本章将深入探讨 Vue 3 响应式系统的工作原理。
响应式基础
Vue 3 的响应式系统基于 ES6 的 Proxy 实现,相比 Vue 2 的 Object.defineProperty 有了显著的改进。
Proxy vs Object.defineProperty
javascript
// Vue 2 方式 (Object.defineProperty)
Object.defineProperty(obj, 'count', {
get() {
return value
},
set(newValue) {
value = newValue
// 触发更新
}
})
// Vue 3 方式 (Proxy)
const proxy = new Proxy(obj, {
get(target, key) {
track(target, key) // 依赖收集
return target[key]
},
set(target, key, value) {
target[key] = value
trigger(target, key) // 触发更新
return true
}
})响应式 API
ref()
ref() 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
javascript
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1reactive()
reactive() 返回一个对象的响应式代理。
javascript
import { reactive } from 'vue'
const state = reactive({
count: 0,
name: 'Vue'
})
state.count++ // 响应式更新readonly()
创建一个只读的响应式对象:
javascript
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 修改 original 会触发依赖于 copy 的侦听器
original.count++
// 修改 copy 会失败并导致警告
copy.count++ // 警告!依赖收集与触发
依赖收集 (track)
当响应式对象的属性被访问时,Vue 会收集当前正在执行的副作用函数作为依赖:
javascript
let activeEffect = null
function track(target, key) {
if (activeEffect) {
// 将当前副作用函数添加到依赖集合中
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
}触发更新 (trigger)
当响应式对象的属性被修改时,Vue 会触发所有相关的副作用函数:
javascript
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}计算属性的实现
计算属性是基于响应式依赖进行缓存的:
javascript
import { ref, computed } from 'vue'
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
count.value = 2
console.log(plusOne.value) // 3计算属性的内部实现:
javascript
function computed(getter) {
let value
let dirty = true
const effect = new ReactiveEffect(getter, () => {
if (!dirty) {
dirty = true
// 触发计算属性的依赖更新
}
})
return {
get value() {
if (dirty) {
value = effect.run()
dirty = false
}
return value
}
}
}侦听器的实现
侦听器允许我们在响应式状态发生变化时执行副作用:
javascript
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})响应式系统的优势
- 更好的性能:Proxy 可以监听整个对象,而不需要遍历所有属性
- 支持数组索引和长度变化:可以检测数组的索引赋值和长度变化
- 支持 Map、Set、WeakMap、WeakSet:原生支持更多数据结构
- 更好的类型推导:TypeScript 支持更好
响应式系统的限制
虽然 Vue 3 的响应式系统很强大,但仍有一些限制需要注意:
- 解构赋值会失去响应性:
javascript
const state = reactive({ count: 0 })
let { count } = state // count 不再是响应式的- 将响应式对象的属性赋值给本地变量:
javascript
const state = reactive({ count: 0 })
let count = state.count // count 不是响应式的最佳实践
- 优先使用
ref()处理基本类型 - 使用
reactive()处理对象类型 - 使用
toRefs()解构响应式对象 - 避免直接解构响应式对象
javascript
import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Vue' })
// 正确的解构方式
const { count, name } = toRefs(state)