持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情 reactive API对传入的target类型有限制(必须是对象或者数组类型),并不支持一些基本数据类型(如string,number和boolen等)
因此Vue3设计了ref API
3.2版本前
Vue3.2版本对ref API也做了一些性能优化,我们先看一3.2版本之前的ref实现
function ref(value) {
return createRef(value)
}
const convert = (val) => isObject(val) ? reactive(val) : val
function createRef(rawValue,shallow=false) {
if(isRef(rawValue)) {
// 如果传入的本身就是ref,返回其自身就行,处理嵌套ref的情况
return rawValue
}
return new RefImpl(rawValue,shallow)
}
class RefImpl {
constructor(_rawValue,_shallow=false) {
this._rawValue = _rawValue
this._shallow = _shallow
this._v_isRef = true
// 非shallow时,执行原始值的转换
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
// 给value属性添加getter,并做依赖收集
track(toRaw(this), 'get', 'value')
return this._value
}
set value(newVal) {
// 给value属性添加setter
if(hasChanged(toRaw(newVal),this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
// 派发通知
trigger(toRaw(this),'set'.'value',newVal)
}
}
}
ref函数返回了执行createRef函数的返回值。在createRef内部,首先处理了嵌套ref的情况,接着返回了RefImpl对象的实例。
RefImpl内部主要是劫持其实例value属性的getter和setter。
当访问一个ref对象的value属性时,会触发getter,执行track函数做依赖收集,然后返回它的值;
当修改ref对象的value的时候,会触发setter,设置新值并执行trigger函数派发通知,注意!如果新值newVal是对象或者数组类型,那么把它转换成一个reactive对象
3.2版本的优化
class RefImpl {
constructor(_rawValue,_shallow=false) {
this._shallow = _shallow
this.dep = undefined
this._v_isRef = true
this._rawValue = _shallow ? value : toRaw(value)
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this._shallow ? newVal : toRaw(newVal)
if(hasChanged(newVal,this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
triggerRefValue(this,newVal)
}
}
}
主要的改动就是对ref对象的value属性执行依赖收集和派发通知的逻辑
trackRefValue依赖收集
在Vue3.2版本中的ref实现中,依赖收集部分由原先的track函数变成了trackRefValue,看一下它的逻辑
function trackRefValue(ref) {
if(isTracking()) {
ref = toRaw(ref)
if(!ref.dep) {
ref.dep = createDep()
}
if((process.env.NODE_ENV !== 'production')) {
trackEffects(ref.dep, {
target: ref,
type:'get',
key: 'value'
})
} else {
trackEffects(ref.dep)
}
}
}
这里直接把ref相关的依赖保存到了dep属性中,而在track函数的实现中,会把依赖保留到全局 的targetMap中
let depsMap = targetMap.get(target)
if(!depsMap) {
// 每个target对应一个depsMap
targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key)
if(!dep) {
// 每个key对应一个dep集合
depsMap.set(key,(dep = createDep()))
}
显然,track函数内部可能需要做多次判断和设置逻辑,而把依赖保存到ref对象的dep属性中则省去了这一系列的判断和设置,从而优化了性能
triggerRefValue派发通知
与之相应的ref的派发通知部分由原先的trigger函数变成了triggerRefValue
function triggerRefValue(ref,newVal) {
ref = toRaw(ref)
if(ref.dep) {
if((process.env.NODE_ENV !== 'production')) {
trackEffects(ref.dep, {
target: ref,
type:'set',
key: 'value',
newValue: newVal
})
} else {
triggerEffects(ref.dep)
}
}
}
function triggerEffects(dep,debuggerEventExtraInfo) {
for(const effect of isArray(dep) ? dep : [...dep]) {
if(effect !== activeEffect || effect.allowRecurse) {
if((process.env.NODE_ENV !== 'production') && effect.onTrigger) {
effect.onTrigger(extend({effect},debuggerEventExtraInfo))
}
if(effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
由于能直接从ref属性中获取其所有的依赖并遍历执行,不需要trigger函数的一些额外逻辑,因此性能得到了提升。