vue3 响应式原理简单版分析

83 阅读3分钟

我们知道Vue3通过proxy来实现响应式,总的来说就是getter中收集依赖,setter中触发依赖。提供的reactive可以将数据变成响应式,我们来看下reactive的实现方式。

function reactive (target) {

  // 如果尝试把一个 readonly proxy 变成响应式,直接返回这个 readonly proxy
  if (target && target.__v_isReadonly) {
     return target
  } 

  return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {

  if (!isObject(target)) {

    // 目标必须是对象或数组类型
    if ((process.env.NODE_ENV !== 'production')) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }

  if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
    // target 已经是 Proxy 对象,直接返回
    return target
  }


  // 利用 Proxy 创建响应式

  const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)

  // 给原始数据打个标识,说明它已经变成响应式,并且有对应的 Proxy 了

  def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)

  return observed

}

可以看到,reactive内部调用的还是createReactiveObject函数。

  1. createReactiveObject内部首先判断target是不是对象,因为reactive作用的对象是Object。
  2. 如果对一个已经是响应式的对象再次执行 reactive,还应该返回这个响应式对象。
  3. 重点:通过 Proxy 创建响应式

接下来就看Proxy中的处理器,里面会涉及到getter、setter。接下来看看如何在getter中收集依赖,setter中触发依赖。

依赖收集:get 函数

以下代码只涉及到关键部分,细节部分省略。

function createGetter(isReadonly = false) {

  return function get(target, key, receiver) {

    ......

    // 求值
    const res = Reflect.get(target, key, receiver)

    // 依赖收集
    !isReadonly && track(target, "get" /* GET */, key)
    return isObject(res)
      ? isReadonly
        ?
        readonly(res)
        // 如果 res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式
        : reactive(res)
      : res
  }
}

可以看到我们是 先通过 Reflect.get 求值,然后再执行 track 函数收集依赖.

track函数

track函数中我们需要了解一个思路,响应式作用的目标target是对象,对象有好多属性,通过map存储;每个属性可能在不同地方使用,因此对应不同的副作用,存储到Set中防止重复。整个的关系就是:

image.png

// 是否应该收集依赖
let shouldTrack = true

// 当前激活的 effect
let activeEffect

// 原始数据对象 map
const targetMap = new WeakMap()

function track(target, type, key) {

  if (!shouldTrack || activeEffect === undefined) {

    return

  }

  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 = new Set()))

  }

  if (!dep.has(activeEffect)) {

    // 收集当前激活的 effect 作为依赖

    dep.add(activeEffect)

   // 当前激活的 effect 收集 dep 集合作为依赖

    activeEffect.deps.push(dep)

  }

}

全局有很多响应式对象,我们将这些响应式对象也放到一个Map中存储,这里我们使用WeakMap存储,使用weakMap目的是能将 不再使用的数据进行正确的垃圾回收
每次 track ,就是把当前激活的副作用函数 activeEffect 作为依赖,然后收集到 target 相关的 depsMap 对应 key 下的依赖集合 dep 中。

trigger函数


// 原始数据对象 map

const targetMap = new WeakMap()

function trigger(target, type, key, newValue) {

  // 通过 targetMap 拿到 target 对应的依赖集合

  const depsMap = targetMap.get(target)

  if (!depsMap) {

    // 没有依赖,直接返回

    return

  }

  // 创建运行的 effects 集合

  const effects = new Set()

  // 添加 effects 的函数

  const add = (effectsToAdd) => {

    if (effectsToAdd) {

      effectsToAdd.forEach(effect => {

        effects.add(effect)

      })

    }

  }

  // SET | ADD | DELETE 操作之一,添加对应的 effects

  if (key !== void 0) {

    add(depsMap.get(key))

  }

  const run = (effect) => {

    // 调度执行

    if (effect.options.scheduler) {

      effect.options.scheduler(effect)

    }

    else {

      // 直接运行

      effect()

    }

  }

  // 遍历执行 effects

  effects.forEach(run)

}

trigger函数与track函数对应的,主要做了四件事情:

  1. 通过 targetMap 拿到 target 对应的依赖集合 depsMap;
  2. 创建运行的 effects 集合;
  3. 根据 key 从 depsMap 中找到对应的 effects 添加到 effects 集合;
  4. 遍历 effects 执行相关的副作用函数。

所以每次 trigger 函数就是根据 target 和 key ,从 targetMap 中找到相关的所有副作用函数遍历执行一遍。