你了解Vue.js 3.0的响应式原理吗?

236 阅读5分钟

最近正在学习Vue.js 3.0,这里记录下响应式原理相关知识。

响应式系统原理

Vue.js 3.0采用Proxy对象实现属性的监听。在初始化的时候不需要遍历所有的属性,再把属性通过defineProperty转换为getter和setter。如果有多层属性嵌套,只有访问某个属性的时候才会递归处理下一级的属性,所以Vue.js 3.0中响应式的性能比Vue.js 2.x好。Vue.js 3.0的响应式系统默认可以监听动态添加的属性,还可以监听属性的删除操作,以及监听数组的索引和length操作。另外,Vue.js 3.0的响应式系统还可以作为单独的模块使用。

模拟响应式原理的实现

Vue3 使用 Proxy 对象重写响应式系统,这个系统主要有以下几个函数来组合完成的:

  1. reactive:
  • 接收一个参数,判断这参数是否是对象。不是对象则直接返回这个参数,不做响应式处理
  • 创建拦截器对象 handler, 设置 get/set/deleteProperty
    • get
      • 收集依赖(track)
      • 返回当前 key 的值。
      • 如果当前 key 的值是对象,则为当前 key 的对象创建拦截器 handler, 设置 get/set/deleteProperty
      • 如果当前的 key 的值不是对象,则返回当前 key 的值
    • set
      • 设置的新值和老值不相等时,更新为新值,并触发更新(trigger)
    • deleteProperty
      • 当前对象有这个 key 的时候,删除这个 key 并触发更新(trigger)
      • 返回 Proxy 对象
  1. effect:
  • 接收一个函数作为参数。
  • 作用是:访问响应式对象属性时去收集依赖
  1. track:
  • 接收两个参数:target 和 key
  • 如果没有 activeEffect,则说明没有创建 effect 依赖
  • 如果有 activeEffect,则去判断 WeakMap 集合中是否有 target 属性,
    • WeakMap 集合中没有 target 属性,则 set(target, (depsMap = new Map()))
    • WeakMap 集合中有 target 属性,则判断 target 属性的 map 值的 - - depsMap 中是否有 key 属性
      • depsMap 中没有 key 属性,则 set(key, (dep = new Set()))
      • depsMap 中有 key 属性,则添加这个 activeEffect
  1. trigger:
  • 判断 WeakMap 中是否有 target 属性
    • WeakMap 中没有 target 属性,则没有 target 相应的依赖
    • WeakMap 中有 target 属性,则判断 target 属性的 map 值中是否有 key 属性,有的话循环触发收集的 effect()
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)

export function reactive (target) {
  // reactive接收一个参数,判断这个参数是否是对象,否则返回。
  if (!isObject(target)) return target

  // 创建拦截器对象handler,设置get/set/deleteProperty。
  const handler = {
    get (target, key, receiver) {
      // 调用track函数收集依赖
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      // 如果是嵌套属性,需要递归处理下一级的属性
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true
      if (oldValue !== value) {
        result = Reflect.set(target, key, value, receiver)
        // 调用trigger函数触发更新
        trigger(target, key)
      }
      return result
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key)
      const result = Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        // 调用trigger函数触发更新
        trigger(target, key)
      }
      return result
    }
  }
  // 返回Proxy对象
  return new Proxy(target, handler)
}

let activeEffect = null // 存储callback,让后续的track函数可以访问callback
export function effect (callback) {
  // effect接收一个函数作为参数
  activeEffect = callback
  callback() // 访问响应式对象属性,去收集依赖
  activeEffect = null // 收集依赖的时候如果有嵌套属性,需要递归处理,所以需要把activeEffect设为null
}

let targetMap = new WeakMap()

// track()收集依赖
// track接收两个参数:目标对象和跟踪的属性
export function track (target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target) // 寻找的字典,包括属性和对应的dep(也就是函数)
  if (!depsMap) {
   // 如果当前的 depsMap 没有值,需要创建一个对应的depsMap,并添加到targetMap中
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

// trigger()触发更新,需要去targetMap中找到属性对应的effect()来执行
export function trigger (target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => {
      effect()
    })
  }
}

// ref接受一个内部值并返回一个响应式且可变的 ref 对象。
// ref 对象具有指向内部值的单个 property.value。
// 如果将对象分配为 ref 值,则通过 reactive 方法使该对象具有高度的响应式。
export function ref (raw) {
  // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
  if (isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw)
  const r = {
    __v_isRef: true,
    get value () {
      track(r, 'value')
      return value
    },
    set value (newValue) {
      if (newValue !== value) {
        raw = newValue
        value = convert(raw)
        trigger(r, 'value')
      }
    }
  }
  return r
}
// toRefs函数接收reactive返回的响应式对象
// 如果传入的参数不是reactive创建的响应式对象就直接返回
// 然后再将传入对象的所有属性转化为类似ref返回的对象
// 把转化后的属性挂载到一个新的对象上返回
export function toRefs (proxy) {
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}

  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key)
  }

  return ret
}
// 将传入参数转化成ref返回的对象
function toProxyRef (proxy, key) {
  const r = {
    __v_isRef: true,
    get value () {
      return proxy[key]
    },
    set value (newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

// 接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式ref对象。
export function computed (getter) {
  const result = ref()

  effect(() => (result.value = getter()))

  return result
}

rective VS ref

  • ref可以把基本数据类型数据转成响应式对象,当获取数据时要通过value属性,模板中使用的时候可以省略value。返回的对象,重新赋值成对象也是响应式的。
  • reactive返回的对象,重新赋值丢失响应式。返回的对象不可以解构,如果解构的话需要使用toRefs来处理reactive返回的对象