美文网首页
2024-04-25 vue3 ref reactive 源码响

2024-04-25 vue3 ref reactive 源码响

作者: 忙于未来的民工 | 来源:发表于2024-04-25 11:29 被阅读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属性。

    相关文章

      网友评论

          本文标题:2024-04-25 vue3 ref reactive 源码响

          本文链接:https://www.haomeiwen.com/subject/cargxjtx.html