Vue3的ref(源码)

84 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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函数的一些额外逻辑,因此性能得到了提升。