Vue3 源码学习(三):响应式系统

84 阅读2分钟

响应式系统

什么是响应式系统?如下代码:

const info = {
  name: 'caohan',
  age: 18,
  height: 1.8,
  counter: 10
}

function doubleCounter() {
  console.log(info.counter * 2)
}
...

doubleCounter 函数对 info.counter 有依赖。当 info.counter 的值改变时,这个函数并不会重新执行。

响应式即:只要 info.counter 的值改变,所有对它有依赖的地方都会重新执行。即:

// 当 info.counter++ 时,应该执行以下代码:
doubleCounter()
...

以以上代码为例,我们一步一步的实现响应式的系统。

实现思路:收集依赖、监听改变、执行依赖

首先第一步,我们构造一个类并把所有依赖加入订阅:

// 依赖相关类
class Dep {
  constructor() {
    // 订阅者:存储依赖
    this.subscribers = []
    // this.subscribers = new Set()
  }

  // 添加依赖
  addEffect(effect) {
    this.subscribers.push(effect)
    // this.subscribers.add(effect)
  }

  notify() {
    // 执行所有的依赖
    this.subscribers.forEach((effect) => {
      effect()
    })
  }
}

const dep = new Dep()
// 把所有的依赖加入订阅
dep.addEffect(doubleCounter)
...

当数据改变时,我们就直接调用依赖的执行方法:

// 数据改变
info.counter++
// 执行所有依赖
dep.notify()

这样做的好处是我们不用一个个的调用依赖的函数,缺点是不管是收集依赖还是调用都是手动来执行的。

为了实现自动收集依赖及数据改变时自动调用依赖,我们需要对以上代码做一个改造。

实现依赖的自动分类和自动调用

定义依赖相关类

// 定义依赖类
class Dep {
  constructor() {
    this.subscribers = []
  }
  
  // 添加依赖
  depend() {
    if (activeEffect) {
      this.subscribers.push(activeEffect)
    }
  }

  // 执行所有的依赖
  notify() {
    this.subscribers.forEach((effect) => {
      effect()
    })
  }
}

定义添加依赖的函数

let activeEffect = null
// 定义添加依赖的函数
function watchEffect(effect) {
  activeEffect = effect
  activeEffect = null
  effect() // 传入时默认执行一次
}

封装根据不同属性获取对应依赖的工具函数

const targetObj = {}

function getDep(target, key) {
  let depsObj = targetObj[target]
  if (!depsObj) {
    depsObj = {}
    targetObj[target] = depsObj
  }

  let dep = depsObj[key]
  if (!dep) {
    dep = new Dep()
    depsObj[key] = dep
  }

  return dep
}

数据劫持函数

function reactive(raw) {
  // vue2 实现
  Object.keys(raw).forEach(key => {
    const dep = getDep(raw, key)
    let value = raw[key]

    Object.defineProperty(raw, key, {
      get() {
        dep.depend()
        return value
      },
      set(newVal) {
        if (value !== newVal) {
          value = newVal
          dep.notify()
        }
      }
    })
  })
  
  return raw
  
  
  // vue3实现
  return new Proxy(raw, {
    get(target, key) {
      const dep = getDep(target, key)
      dep.depend()
      return target[key]
    },
    set(target, key, newVal) {
      const dep = getDep(target, key)
      target[key] = newVal
      dep.notify()
    }
  })
}

数据处理及添加依赖

const info = reactive({ counter: 100, price: 10 })
const foo = reactive({ name: 'kobe' })

watchEffect(function () {
  console.log(1, info.counter);
})
watchEffect(function () {
  console.log(2, info.price);
})
watchEffect(function () {
  console.log(3, info.name);
})

info.counter++