Vue 3 响应式系统架构详解
1. 响应式系统概述
Vue 3的响应式系统是其核心特性之一,它使得数据变化能够自动触发视图更新。与Vue 2相比,Vue 3的响应式系统基于ES6的Proxy API重构,提供了更好的性能和更丰富的功能。
2. 核心概念
2.1 依赖收集与触发更新
在Vue 3的响应式系统中,有两个核心概念:
- 依赖收集:当访问响应式数据时,系统会记录哪些副作用函数(effect)依赖于这些数据
- 触发更新:当响应式数据发生变化时,系统会通知所有依赖该数据的副作用函数重新执行
2.2 副作用函数
副作用函数(effect)是响应式系统中的关键概念,它代表了需要在数据变化时重新执行的代码块。在Vue 3中,通过effect函数来创建副作用函数。
3. 核心模块详解
3.1 reactive模块
reactive函数用于创建响应式对象。它通过Proxy代理原始对象,拦截get和set操作。
javascript
const reactive = (obj) => {
return createReactive(obj)
}
const createReactive = (obj) => {
if(!isObject(obj)) {
return obj
}
const handlers = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
if(isObject(result)) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
}
}
const proxy = new Proxy(obj, handlers)
proxyMap.set(obj, proxy)
return proxy
}
3.2 ref模块
ref函数用于创建响应式的基本数据类型。它通过包装一个对象来实现响应式。
javascript
const ref = (value) => {
return createRef(value)
}
const createRef = (value, isShallow) => {
if(isRef(value)) {
return value
}
return new RefImpl(value, isShallow)
}
class RefImpl {
#__v_isRef = true;
constructor(value, isShallow) {
this.#_value = isShallow ? value : toReactive(value)
this.#__raw_value = value
}
get value() {
trackEffect(this)
return this.#_value
}
set value(newValue) {
if(newValue === this.#__raw_value) {
return
}
this.#__raw_value = newValue
this.#_value = toReactive(newValue)
triggerEffect(this)
}
}
3.3 computed模块
computed函数用于创建计算属性,它会缓存计算结果并在依赖变化时重新计算。
javascript
const computed = (getterOrOptions, debugOptions) => {
let getter;
let setter;
if(isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {
console.warn('Write operation failed: computed value is readonly');
}
} else {
getter = getterOrOptions.getter;
setter = getterOrOptions.setter;
}
const cRef = new ComputedRefImpl(getter, setter)
return cRef
}
class ComputedRefImpl {
#__v_isRef = true;
is_dirty = true;
constructor(getter, setter) {
this._setter = setter
this.effect = new ReactiveEffect(getter, () => {
if(!this.is_dirty) {
this.is_dirty = true;
triggerEffect(this)
}
})
}
get value() {
trackEffect(this)
if(this.is_dirty) {
this.is_dirty = false;
this._value = this.effect.run()
}
return this._value
}
set value(newValue) {
this._setter(newValue);
}
}
4. 关键函数详解
4.1 track函数
track函数负责依赖收集,它记录当前活跃的副作用函数与目标对象属性之间的依赖关系。
javascript
const track = (target, key) => {
if(!activeEffect) {
return
} else {
let depsMap = targetMap.get(target)
if(!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key);
if(!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
}
}
4.2 trigger函数
trigger函数负责触发更新,它通知所有依赖于特定属性的副作用函数重新执行。
javascript
const trigger = (target, key) => {
let depsMap = targetMap.get(target)
if(!depsMap) {
return
}
let deps = depsMap.get(key)
if(!deps) {
return
}
const effectsArr = [...deps]
effectsArr.forEach(effect => {
effect.run()
})
}
4.3 effect函数
effect函数用于创建和执行副作用函数。
javascript
const effect = (fn) => {
const _effect = new ReactiveEffect(fn);
_effect.run()
}
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn
this.scheduler = scheduler
}
run() {
activeEffect = this
return this.fn()
}
}
5. 响应式系统架构图
原始对象
Proxy代理
get操作
set操作
track依赖收集
trigger触发更新
依赖关系存储
副作用函数执行
WeakMap存储
视图更新
6. 性能优化要点
Vue 3的响应式系统在性能方面做了多项优化:
- 使用WeakMap:避免内存泄漏,当对象被垃圾回收时,相关的依赖关系也会被自动清理
- 使用Proxy:相比Vue 2的Object.defineProperty,Proxy可以监听数组变化和对象新增属性
- 懒执行:计算属性只有在被访问时才会执行,避免不必要的计算
7. 实际应用示例
javascript
const a = ref(5)
const com = computed(() => {
console.info("cmpted", a.value)
return a.value * 2
})
setTimeout(() => {
a.value = 6
console.info("afterTime", com.value)
}, 1000)
console.info(com.value)
在这个示例中,当a.value发生变化时,计算属性com会自动重新计算并输出新的值。
8. 总结
Vue 3的响应式系统通过Proxy、WeakMap和Effect等机制,实现了高效、灵活的数据响应式处理。理解这些核心概念和实现原理,有助于更好地使用Vue 3进行开发。
9. 完整代码
js
// import { isObject } from "lodash"
let proxyMap = new WeakMap()
let targetMap = new WeakMap()
let activeEffect = null
// ------------- reactive ---------------
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn
this.scheduler = scheduler
}
run() {
activeEffect = this
return this.fn()
}
}
const reactive = (obj) => {
return createReactive(obj)
}
const isObject = (obj) => {
if(obj === null) {
return true
} else if(typeof obj === 'object') {
return true
} else {
return false
}
}
const createReactive = (obj) => {
if(!isObject(obj)) {
return obj
}
const handlers = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
if(isObject(result)) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
}
}
const proxy = new Proxy(obj, handlers)
proxyMap.set(obj, proxy)
return proxy
}
const track = (target, key) => {
// console.info('track-activeEffect', activeEffect)
if(!activeEffect) {
return
} else {
let depsMap = targetMap.get(target)
if(!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key);
if(!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
// console.info('targetMap', targetMap)
}
}
const trigger = (target, key) => {
let depsMap = targetMap.get(target)
if(!depsMap) {
return
}
let deps = depsMap.get(key)
if(!deps) {
return
}
const effectsArr = [...deps]
effectsArr.forEach(effect => {
effect.run()
})
}
const effect = (fn) => {
const _effect = new ReactiveEffect(fn);
_effect.run()
}
// -------------------------- ref ---------------------------
const isRef = (value) => {
return value && value.__v_isRef === true
}
const ref = (value) => {
return createRef(value)
}
const toReactive = (value) => isObject(value) ? reactive(value) : value;
const createRef = (value, isShallow) => {
if(isRef(value)) {
return value
}
return new RefImpl(value, isShallow)
}
class RefImpl {
#_value;
#__raw_value;
deps;
__v_isRef = true;
constructor(value, isShallow) {
this.#_value = isShallow ? value : toReactive(value)
this.#__raw_value = value
}
get value() {
trackEffect(this)
return this.#_value
}
set value(newValue) {
if(newValue === this.#__raw_value) {
return
}
this.#__raw_value = newValue
this.#_value = toReactive(newValue)
triggerEffect(this)
}
}
const trackEffect = (ref) => {
if(activeEffect) {
if(!ref.deps) {
ref.deps = new Set()
}
ref.deps.add(activeEffect)
}
}
const triggerEffect = (ref) => {
if(ref.deps) {
let depsArr = [...ref.deps]
depsArr.forEach(effect => {
if(effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
})
}
}
// -------------------------- computed ---------------------------
const isFunction = (value) => {
return typeof value === 'function'
}
const computed = (getterOrOptions, debugOptions) => {
let getter;
let setter;
if(isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {
console.warn('Write operation failed: computed value is readonly');
}
} else {
getter = getterOrOptions.getter;
setter = getterOrOptions.setter;
}
const cRef = new ComputedRefImpl(getter, setter)
return cRef
}
class ComputedRefImpl {
#__v_isRef
is_dirty = true;
deps;
constructor(getter, setter) {
this._setter = setter
this.effect = new ReactiveEffect(getter, () => {
if(!this.is_dirty) {
this.is_dirty = true;
triggerEffect(this)
}
})
this.#__v_isRef = true;
}
get value() {
trackEffect(this)
if(this.is_dirty) {
this.is_dirty = false;
this._value = this.effect.run()
}
return this._value
}
set value(newValue) {
this._setter(newValue);
}
}
const a = ref(5)
const com = computed(() => {
console.info("cmpted", a.value)
return a.value * 2
})
setTimeout(() => {
a.value = 6
console.info("afterTime", com.value)
}, 1000)
console.info(com.value)
// const a = ref(4)
// effect(() => {
// console.info("effect-handle", a.value)
// })
// effect(() => {
// console.info("effect-handle2222", a.value)
// })
// effect(() => {
// console.info("effect-handle2222334", a.value)
// })
// setTimeout(()=>{
// a.value = 65
// },1000)