Vue 3.0 中的 reative 和 Vue 2.6 中提供的一个全局 API Vue.observable 雷同,都是用于让一个对象可响应,首先来比照一下他们之间的差别:

reactiveVue.observable
基于 Proxy 实现基于 Object.defineProperty  实现
对 代理对象 进行操作间接操作 源对象
返回一个可响应的 代理对象返回一个可响应的 源对象

reactivity工作流程

在 Vue 3.0 中, reactivity 被独立进去,没有任何依赖,能够用于任何想做响应式数据的中央
先抛开源码中实现的简单判断,来看一下他的次要工作流程

要害办法实现

本文次要实现了其中几个要害的办法:

reactive

  • 创立响应式对象,在 Proxy 中定义 get 及 set 捕捉器,对传入的 源对象 代理对象 进行拦挡解决
  • get 捕捉到以后对象的属性也是对象,要进行递归
  • 定义基于 WeakMap 的 reactiveMap 治理代理对象,如果传入的 object 曾经有记录,间接返回 此对象代理对象 ,如果没有,依照失常流程走
/** * 处理器对象,定义捕捉器 */const handlers = {  set(target, key) {    Reflect.set(...arguments)    trigger(target, key)  },  get(target, key) {    track(target, key)    return typeof target[key] === 'object'      ? reactive(target[key])      : Reflect.get(...arguments)  },}/** * 定义响应式对象,返回proxy代理对象 * @param {*} object */function reactive(object) {  if (reactiveMap.has(object)) return reactiveMap.get(object)  const proxy = new Proxy(object, handlers)  reactiveMap.set(object, proxy)  return proxy}

effect

  • 副作用,创立用于治理 effect 的栈 effectStack ,将 effect 先入栈用于依赖收集,执行一次该 effect ,进入 get 捕捉阶段,捕捉结束之后进入 finally 将其在栈中移出
/** * 副作用函数 * @param {*} fn */function effect(fn) {  try {    // 将须要执行的effect入栈,用于依赖收集过程中与key的关系对应    effectStack.push(fn)    // 执行该effect,进入proxy的get拦挡    return fn()  } finally {    // 依赖收集结束及所有get流程走完,以后effect出栈    effectStack.pop()  }}

track

  • effect 执行后数据触发 get 捕捉器, 在此过程中调用 track 进行依赖收集
  • 定义 targetMap ,以 WeakMap 的形式收集依赖,治理指标对象 target 及其对应的 key 
  • 第二层用于治理 key 及其对应的 effect ,下面流程图能够看到数据的构造和档次划分
/** * 依赖收集 * @param {*} target * @param {*} key */function track(target, key) {  // 初始化依赖Map  let depsMap = targetMap.get(target)  if (!depsMap) {    targetMap.set(target, (depsMap = new Map()))  }  // 第二层依赖应用Set寄存key对应的effect  let dep = depsMap.get(key)  if (!dep) {    targetMap.get(target).set(key, (dep = new Set()))  }  // 取以后栈中的effect存入第二层依赖中  const activeEffect = effectStack[effectStack.length - 1]  activeEffect && dep.add(activeEffect)}

trigger

  • 批改在 effect 中指定过的内容时会触发 set 捕捉器,在此过程中 trigger 负责执行以后 target 下 key 对应的 effect ,实现响应式的过程
/** * 触发响应,执行effect * @param {*} target * @param {*} key */function trigger(target, key) {  const depsMap = targetMap.get(target)  if (depsMap) {    const effects = depsMap.get(key)    effects && effects.forEach(run => run())  }}

computed

  • 这里采纳简略粗犷的形式,间接返回一个 effect 
/** * 计算属性 * @param {*} fn  */function computed(fn) {  return {    get value() {      return effect(fn)    },  }}

成果展现

对象属性响应式,多层嵌套

const object = {  o: {    a: 1  }}  const proxy = reactive(object)effect(() => {  console.log(`proxy.o.a: ${proxy.o.a}`)})
  • 首次调用打印一次,从新赋值后再次响应,调用 effect 


响应式调色器

  • 配置响应式对象,指定其rgb属性,顺便测一下 computed 
const object = {  r: 0,  g: 0,  b: 0}const proxy = reactive(object)const computedObj = computed(() => proxy.r * 2)effect(() => {  const { r, g, b } = proxy  document.getElementById('r').value = r  document.getElementById('b').value = b  document.getElementById('g').value = g  document.getElementById(    'color'  ).style.backgroundColor = `rgb(${r},${g},${b})`  document.getElementById('color_text').innerText = `rgb:${r},${g},${b}`  const { value } = computedObj  document.getElementById(    'computed_text'  ).innerText = `computed_text: r*2=${value}`})
  • 拖动 rgb 3个 range 时各自的变动会体现在色彩块上

  • 对象依赖关系 targetMap 构造如下

  • reactiveMap 构造如下


总结

  • 通过上述内容能够理解到 Vue 3.0 中的响应式原理

    • reactive 创立响应式对象
    • effect 副作用,调用本身收集依赖,数据变更后从新调用该函数
    • track 依赖收集
    • trigger 触发依赖中对应的 effect 
    • computed 计算属性,对应属性值变更调用其 effect 
  • 过程中还能更加相熟一些前置基础知识

    • 代理与反射: Proxy Reflect 
    • JavaScript 规范内置对象: WeakMap Map Set 
    • 语句执行应用技巧: try finally 

参考资料

  • 残缺代码传送门
  • Proxy
  • Reflect
  • WeakMap
  • Map
  • Set
  • 语句执行
  • Vue 3.0 源码