Vue 核心------响应式系统
今天分享 Vue 的核心之一:响应式系统。文章内容尽量保持简单易懂,并模仿 Copilot 使用 emoji 来增添色彩。
希望大家多提意见,多点赞,感谢。
Vue 2
Vue 2 的响应式系统基于 Object.defineProperty,通过 访问器描述符 对对象的属性进行拦截,从而实现 依赖收集和触发更新 。
下面是根据自己的理解输出的代码,并不与源代码一致。
javascript
// 在 src/core/instance 里面执行 initMixin -> initState -> initData
function initData(vm) {
let data: any = vm.$options.data
observe(data)
}
function observe(data) {
return new Observer(data)
}
class Observer{
constructor(data) {
if(isArray(data)){
observeArray(data)
}else{
const keys = Object.keys(data)
for(let i = 0; i < keys.length; i++){
defineReactive(data, key)
}
}
}
}
function defineReactive(target, key, value = undefined) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(target, key)
if(property && property.configurable === false) return
for(let key in obj){
if(Object.hasOwn(obj, key)){
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend({target, type: TrackOpTypes.GET, key})
return target[key]
},
set(newVal) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
target[key] = newVal
}
})
}
}
}
class Dep{
constructor() {
this.subs:Watcher[] = []
}
depend(sub: Subscriber) {
this.subs.push(sub)
}
notify() {
for(let i = 0; i < this.subs.length; i++) {
subs[i].update()
}
}
}
Vue 3
Vue 3 中抛弃了 Object.defineProperty,转头 Proxy 的怀抱,但是 Proxy 并不能拦截基础类型,所以 Vue 3 中对基础类型和对象类型的处理有区别。
ref
ref 通过将基本类型转换成带 .value 的对象,然后拦截对象的 getter 和 setter,从而实现 依赖收集和触发更新。
javascript
function ref(value) {
return new RefImpl(value);
}
class RefImpl{
constructor(value) {
this._value = value
this.dep = new Dep()
}
get value() {
this.dep.track(this._value)
return this._value
}
set value(newVal) {
this._value = newVal
this.dep.trigger()
}
}
class Dep{
constructor() {
this.head = null;
this.tail = null;
this.subs: Link = null;
}
track(value) {
const link = new Link(value)
addSub(link) // 双向链表操作
}
trigger() {
this.notify()
}
notify() {
const result = traverse(this.subs) // 遍历双向链表
for(let i = 0; i < result.length; i++){
this.subs[i].notify()
}
}
}
class Link {
constructor() {
this.prevDep = this.nextDep = this.prevSub = this.nextSub = null
}
}
ref 案例说明
javascript
const name = ref('zhangsan')
const age = ref(12)
const upperName = computed(() => name.value.toUpperCase())
watchEffect(() => {
console.log(name.value, age.value)
})
ref 链表
name.dep.subs
是一个链表,里面挂着所有依赖 name
的副作用
age.dep.subs
是一个链表,里面挂着所有依赖 age
的副作用
每个 Link.sub
指向一个副作用函数
所以当 name.value
改变时,Vue 会遍历 dep.subs
,执行每个 Link.sub.run()
effect 链表
每个副作用函数也维护一个 deps
链表,里面挂着所有它依赖的响应式数据
当副作用失效(比如组件卸载或 stop()
)时,Vue 会遍历 effect.deps
,从每个 dep.subs
中移除对应的 Link
text
┌──────────────┐ ┌──────────────┐
│ name (ref) │ │ age (ref) │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌────────────────────────────┐ ┌────────────────────────────┐
│ dep (Dep) │ │ dep (Dep) │
│ subs: Link A │ │ subs: Link C │
└──────┬─────────────────────┘ └──────┬─────────────────────┘
│ │
▼ ▼
┌────────────────────────────┐ ┌────────────────────────────┐ ┌────────────────────────────┐
│ Link A │──────▶│ watchEffect (effect) │◀────── │ Link C │
│ sub → watchEffect │ │ deps: Link A Link C │ │ sub → watchEffect │
│ dep → name.dep │◀──────│ │──────▶ │ dep → age.dep │
│ nextSub → Link B │ └────────────────────────────┘ │ nextSub → null │
│ prevSub → null │ │ prevSub → null │
└────────────────────────────┘ └────────────────────────────┘
│
▼
┌────────────────────────────┐ ┌────────────────────────────┐
│ Link B │──────▶ │ computedEffect (effect) │
│ sub → computedEffect │ │ deps: Link B │
│ dep → name.dep │◀────── │ │
│ nextSub → null │ └────────────────────────────┘
│ prevSub → Link A │
└────────────────────────────┘
reactive
reactive 通过 Proxy 来实现 依赖收集和触发更新,但是对普通对象和集合对象有点区别。
javascript
const targetMaps = new Map()
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap,)
}
function createReactiveObject(target, isReadOnly, baseHandler, collectionHandler, proxyMap) {
const handler = TargetType.COLLECTION ? collectionHandler : baseHandler
const proxy = new Proxy(target, handler)
return proxy
}
const mutableHandlers = new MutableReactiveHandler()
class MutableReactiveHandler extends BaseReactiveHandler{
set() {
const result = Reflect.set(target, key, value)
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
}
deleteProperty() {
const result = Reflect.deleteProperty(target, key)
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
return result
}
has() {
const result = Reflect.has(target, key)
track(target, TrackOpTypes.HAS, key)
return result
}
ownKeys() {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
}
class BaseReactiveHandler {
get() {
const res = Reflect.get(target, key, isRef(target) ? target : receiver)
track(target, TrackOpTypes.GET, key)
return res
}
}
const mutableCollectionHandler = {
// Map 和 Set 的所有方法都需要经过 get trap,所以这里只需要定义 get trap
get: createInstrumentationGetter(false, false),
}
function createInstrumentationGetter() {
const instrumentations = createInstrumentations(isReadonly, shallow)
return Reflect.get(hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver,
)
}
function createInstrumentations() {
const instrumentations = {
get() {
track(rawTarget, TrackOpTypes.GET, rawKey)
return wrap(target.get(key))
}
get size() {
track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return target.size
}
has() {
track(rawTarget, TrackOpTypes.HAS, rawKey)
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey)
}
forEach() {
track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
return target.forEach((value: unknown, key: unknown) => {
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
}
extend(instrumentations, {
add() {
trigger(target, TriggerOpTypes.ADD, value, value)
return this
}
set() {
trigger(target, TriggerOpTypes.ADD, key, value)
return this
}
delete() {
const result = target.delete(key)
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
return result
}
clear() {
const result = target.clear()
trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
return result
}
})
}
return instrumentations
}
Vue 2 和 Vue 3 的响应式异同
1️⃣ Vue 2 基于 Object.defineProperty,Vue 3 基于 对象的 getter/setter 和 Proxy/Reflect。
2️⃣ Vue 2 的依赖收集在一个 数组,Vue 3 的 ref 依赖收集在 双向链表,reactive 的依赖收集在 Map 集合对象。
3️⃣ Vue 2 中 Watcher 用于处理 渲染、自定义 watch、computed。Vue 3 中使用 ReactiveEffect 代替。