Vue3响应式原理傻瓜式教程(一)——Reactive

971 阅读2分钟

参考教程:Vue Mastery

理解响应式

什么是响应式呢?举个简单的例子。

let price = 5;
let quantity = 2;
let total = price * quantity; // 10

现在我们将price改为6,期望total值也能自动计算更新为12。
而这个自动计算更新,就是响应式。
这一节我们先来谈论一下,如何触发计算更新。

如何实现更新计算结果

let price = 5;
let quantity = 2;
let total = 0;

let dep = new Set() // 相当于收集方法(依赖)的仓库

let effect = () => { // 计算方法
  total = price * quantity
}
function track() { // 添加计算方法到仓库中
  dep.add(effect)
}
function trigger() { // 触发仓库中的方法
  dep.forEach(effect => effect())
}

第一步,执行effect得到初始的结果

effect() // total = 10

第二步,我们需要把effect收集起来,以便于需要的时候再次调用。

track()

第三步,当price发生改变时,再次触发已收集的方法。

price = 6 // 此时total还是10
trigger() // 此时得到total = 12

多个属性怎么办?

上面我们实现了一个计算方法的存储。
实际上,每个对象会有多个属性。
而每个属性,也都有它们各自的dep,来存储一个或多个effect。

let product = { price: 5, quantity: 2 }

所以我们建立起一个大的依赖仓库:depsMap

const depsMap = new Map()

depsMap中,用属性名(如pricequnatity)作为key,用各自的dep作为value
那么,track方法应该这样写:

function track(key) {
  let dep = depsMap.get(key)
  if (!dep) { // 找不到就建一个
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(effect)
}

同时,trigger方法也要改写啦:

function trigger(key) {
  let dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

来运行一下吧:

let product = { price: 5, quantity: 2 }
let total = 0

let effect = () => { 
  total = product.price * product.quantity
}

effect() // total = 10
track('price')

product.price = 6 // total = 10
trigger('price') // total = 12

多个对象怎么办?

上面我们实现了一个对象的响应,接下来我们来探讨下多个对象的收集方法。
我们需要创建一个更大的仓库来存储多个object,每个object都有属于自己的depsMap。

const targetMap = new WeakMap()
// 为什么用WeakMap我们暂时不讨论,目前只需要知道,它的key必须是object类型

function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {  // 找不到就建一个
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(effect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return // 如果没有任何依赖,直接返回
  let dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

来运行一下:

let product = { price: 5, quantity: 2 }
let total = 0
const depsMap = new Map()
let effect = () => { 
  total = product.price * product.quantity
}

effect() // total = 10
track('product', 'price')

product.price = 6  // total = 10
trigger('product', 'price') // total = 12

总结

  • targetMap 用于存放每个响应式object(所有属性)的依赖
  • depsMap 用于存放响应式object每个属性对应的依赖
  • dep 用于存放某个属性对应的所有依赖,当属性发生变化时,会执行依赖 image.png