2024-04-25 vue3 ref reactive 源码响

2024-04-25  本文已影响0人  忙于未来的民工

响应式的实现

1:reactive

shallowReadonly:只读属性,只对第一层代理,不可以修改第一层属性值,一般不用

shallowReactive: 只对第一层代理,可以修改第一层的属性值,一般不用

reactive:对所有属性,包括深层次的属性都进行代理,全部可以修改。

readonly:对所有属性进行代理,包括深层次的属性,全都不可以修改。性能优化

使用proxy代理数据源,在get中进行依赖收集,在set中触发渲染。如果是只读,就不支持set,如果是shallow,只代理第一层属性。

vue2中的代理是直接递归全都代理,而在3中是懒代理,只有使用了才会去走代理。3中代理实现的方式是proxy。

在代理的时候,vue会对已经代理的数据缓存进weakmap,当新数据进行代理,会先从weakmap找,找不到才会进行代理。

2:依赖收集 effect

effect函数只要是用于依赖收集,具体流程是,effect接收一个函数,在函数执行的时候如果使用reactive或者ref变量,就会触发该变量的get方法,在get方法中会将当前活跃的effect放到全局变量 targetMap中。

effect可能会套effect

vue的处理方法是将每个effect放到一个栈中,当前的effect执行完毕后,将当前的effect从栈中剔出,然后将上一个effect设置为活跃的effect。先进后出

const a = reactive({
b: 1,
c: 2
})
effect(() => {
a.b
effect(() => {
 a.c
})
})

在一个effect中调用多次同一个响应式变量,只收集一次。

const a = reactive({
  b: 1
})
effect(() => {
  a.b
  a.b
})

在effect栈中,保证只存一个相同的effect,解决办法在往数组中添加时做下判断

每一个属性的依赖收集中保证只收集一次的方法:在vue中有一个全局变量targetMap,这个map存储着所有的响应式属性的依赖收集,具体结构是

targetMap = {a: {
  b: {effect: effect._trackId} 
// 这个map存储着引用b的effect,在给b的map添加effect时,
//会根据effect的_trackId判断是否已经存在。主要用于过滤重复收集
}}
3:get依赖收集

如果是对象,直接在get方法中将effect放入targetMap中即可。
如果是数组的方法,会进行特殊处理。
具体处理逻辑如下:

['includes', 'indexOf', 'lastIndexOf']

如果是 这三个方法,会对数组的每一个属性进行依赖收集

  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
['push', 'pop', 'shift', 'unshift', 'splice']

如果是 这几个方法,会直接修改数组,接着返回,不进行依赖收集。注意:这几个方法虽然是改变数据源,但是走的是get,而不是set。

  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      pauseTracking()
      pauseScheduling()
      const res = (toRaw(this) as any)[key].apply(this, args)
      resetScheduling()
      resetTracking()
      return res
    }
  })
length

如果是length,走正常的收集逻辑

下标

如果是直接通过下标获取,则进行对象逻辑处理。走正常的收集逻辑。在targetMap中,数组的下标就是key,effect就是value

3:set触发更新

在修改响应式属性的值的时候,会触发代理的set方法,在这个方法中会先判断是新增还是修改,如果是修改会先看老值和新值是否相等,接着就是根据key获取targetMap中的相对应的effect对象,遍历执行相对应的effect函数即可。
如果是直接修改数据的长度,这里的处理是获取使用length对应的effect,以及大于数组新长度的effect。
源码处理逻辑:

if (key === 'length' && isArray(target)) {
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
        deps.push(dep)
      }
    })
  }
4: ref实现

ref内部实现了一个 RefImpl class类,借助于get和set实现的响应式收集和触发更新,本质还是和vue2一样借助于object.defineproperty实现的。
这块要注意的是ref在内部会做下判断,如果接收的是一个对象会走reactive函数,实现响应式。reactive的返回值会赋给 RefImpl的_value属性。

上一篇下一篇

猜你喜欢

热点阅读