Vue3响应式原理设计和实现
- 响应式
- 什么是响应式
- 手动响应式
- proxy代理对象
- 响应式系统
- 一个属性注册一个副作用函数
- 一个属性注册多个副作用函数
- 多个属性注册不同的副作用函数
- 多个数据不同属性注册不同的副作用函数
响应式
什么是响应式
响应式是一个过程,这个过程存在两个参与者:一方触发,另一方响应。所谓响应式的两个参与者:
- 触发者:数据
- 响应者:引用该数据的函数(也叫副作用函数)
手动响应式
当数据改变时,引用该数据的函数响应数据的改变,重新执行。举一个最简单的手动响应式过程的例子:
<div id="app"></div>
const obj = {name: 'Jane'}
const effect = () => {
app.innerHTML = obj.name
}
effect()
setTimeout(() => {
obj.name = 'little Jane'
// 手动执行副作用函数
effect()
}, 1000)
其中,data
就是触发者数据,effect()
就是响应者,也叫副作用函数。
proxy代理对象
new Proxy
传入一个源对象,返回一个新对象(即代理对象),后续对代理对象的操作,都可以自定义访问过程。
const obj = {name: 'Jane'}
const proxy = new Proxy(obj, {
get(target, key) {
// 当访问代理对象的属性时执行 get 函数
return target[key]
},
set(target, key, value) {
// 当设置代理对象的属性时执行 set 函数
target[key] = value
return true
}
})
其中,proxy
就是代理对象, target
是源对象,所以访问代理对象属性时其实返回的还是源对象内容,同样,对代理对象的属性操作其实最终还是操作的是源对象的属性。通过访问代理对象,可以自定义访问过程。
响应式系统
一个属性注册一个副作用函数
通过 proxy代理对象,可以通过 proxy 原理实现一个 reactive 函数,用来将普通对象转化为 proxy 代理对象,并结合响应式系统:
const isObject = value => {
return typeof value === 'object' && value !== null
}
const reactive = obj => {
if(!isObject(obj)) return
return new Proxy(obj, {
get(target, key) {
return target[key]
},
set(target, key, value) {
target[key] = value
effect() // 当设置 state.name 时,重新执行副作用函数
return true
}
})
}
const state = reactive({name: 'Jane'})
const effect = () => {
app.innerHTML = state.name
}
effect()
setTimeout(() => {
state.name = 'little Jane'
}, 1000)
数据的改变,触发关联的副作用函数重新执行,通过 Proxy 代理源数据,在 Proxy 的自定义 set 操作中,重新执行副作用函数。
一个属性注册多个副作用函数
上面的响应式系统是一对一的数据和副作用函数关系。但是,理论上当属性改变时,属性关联的每一个副作用函数的都应该重新执行,这里 state.name
只被 effect
这一个函数引用了,但是如果有多个函数都引用了 state.name
的话,那这多个副作用函数都需要被重新执行。同时,为了避免副作用函数的重复,这里使用 set
类型来存放副作用函数集合:
const bucket = new Set() // 副作用函数桶,用来存放所有的副作用函数
const isObject = value => {
return typeof value === 'object' && value !== null
}
const reactive = obj => {
if(!isObject(obj)) return
return new Proxy(obj, {
get(target, key) {
return target[key]
},
set(target, key, value) {
target[key] = value
bucket.forEach(fn => fn())
return true
}
})
}
const state = reactive({name: 'Jane'})
const effect = () => {
console.log('effect 执行')
}
bucket.add(effect)
const effect1 = () => {
console.log('effect1 执行')
}
bucket.add(effect1)
// 触发 set,在 set 可以自定义执行所有副作用函数
setTimeout(() => {
state.name = 'little Jane'
}, 1000)
多个属性注册不同的副作用函数
当 state
有多个属性时(例如 name
和 age
两个属性),不同的属性发生变化时应该执行对应的副作用函数,而不是执行所有数据的所有副作用函数。需要修改副作用函数桶的结构:
const bucket = new Map() // 副作用函数桶,用来存放所有的副作用函数
let activeEffect = null
const isObject = value => {
return typeof value === 'object' && value !== null
}
// 收集依赖
const track = (target, key) => {
if(!activeEffect) return
let depSet = bucket.get(key)
if(!depSet) {
depSet = new Set()
bucket.set(key, depSet)
}
depSet.add(activeEffect)
}
// 触发副作用函数
const trigger = (target, key) => {
let depSet = bucket.get(key)
if(depSet) {
depSet.forEach(fn => fn())
}
}
const reactive = obj => {
if(!isObject(obj)) return
return new Proxy(obj, {
get(target, key) {
// get 操作时收集依赖
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
// set 操作时,触发副作用函数执行
trigger(target, key)
return true
}
})
}
const effect = fn => {
if(typeof fn !== 'function') return
activeEffect = fn
fn()
activeEffect = null
}
const state = reactive({name: 'Jane', age: 25})
effect(() => {
console.log('get执行', state.name)
})
effect(() => {
console.log('get执行', state.age)
})
console.log(bucket)
多个数据不同属性注册不同的副作用函数
有多个 state
数据时,它们可能有不同属性名,但是也可能会有相同属性名
let activeEffect = null
let bucket = new WeakMap() // [state -> Map[name -> Set(fn, fn), age -> Set(fn, fn)], state1 -> Map]
const isObject = value => {
return typeof value === 'object' && value !== null
}
// 收集依赖
const track = (target, key) => {
if(!activeEffect) return
let depMap = bucket.get(target)
if(!depMap) {
depMap = new Map()
bucket.set(target, depMap)
}
let depSet = depMap.get(key)
if(!depSet) {
depSet = new Set()
depMap.set(key, depSet)
}
depSet.add(activeEffect)
}
// 触发依赖
const trigger = (target, key) => {
let depMap = bucket.get(target)
if(!depMap) return
let depSet = depMap.get(key)
if(depSet) {
depSet.forEach(fn => fn())
}
}
const reactive = obj => {
if(!isObject(obj)) return
return new Proxy(obj, {
get(target, key) {
// get 操作时收集依赖
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
// set 操作时,触发副作用函数执行
trigger(target, key)
return true
}
})
}
const effect = fn => {
if(typeof fn !== 'function') return
activeEffect = fn
fn()
activeEffect = null
}
const state1 = reactive({name: 'Jane', age: 25})
const state2 = reactive({name: 'Qu', age: 27})
effect(() => {
console.log('我在拿 state1.name...', state1.name)
})
effect(() => {
console.log('我在拿 state1.age...', state1.age)
})
effect(() => {
console.log('我在拿 state2.name......', state2.name)
})
effect(() => {
console.log('我又拿 state2.name......', state2.name)
})
console.log(bucket)