带你一行一行手写Vue3.0源码系列(二) -依赖收集和派发更新

前言

上一篇中我们主要讲的Vue3.0响应式的实现,其主要是利用Proxy的来对数据进行劫持。本节课接上一章,主要是讲解Vue3.0是如何在被劫持时进行收集依赖的,同时在修改时是如何派发更新的。

我个人推荐的看源码的方式最好是通过视频文章自己能手写一边然后再去看,否则就会造成这样的结果。

真是卷...

Vue 3.0 中没有了watcher 的概念,但是取而代之的是一个叫 effect的东西 ,所以接下来讲的东西会和 effect有关。

正文

effect(类似于Watcher)

ini 复制代码
// reactivity/effect.js

export function effect(fn, options = {}) { // 类似于vue2的watcher
  const effect = createReactiveEffect(fn, options);
  if (!options.lazy) {
    effect();
  }
  return effect;
}

let uid = 0; // effect的id
let activeEffect; // 保存当前的effect
const effectStack = []; // effect栈结构

function createReactiveEffect(fn, options = {}) {
  const effect = function reactiveEffect() {
    if (!effectStack.includes(effect)) {
      try {
        activeEffect = effect; // 当前最顶层的effect
        effectStack.push(activeEffect); // 入栈
        return fn(); // 执行用户的方法
      } finally {
        // 出栈
        effectStack.pop(); // 出栈
        activeEffect = effectStack[effectStack.length - 1]; // 重置栈结构
      }
    }
  }
  effect.id = uid++; // 用于区别effect
  effect._isEffect = true; // 用户区分我们effect是不是响应式的
  effect.raw = fn; // 保存用户的方法
  effect.options = options; // 保存用户的属性
  effect.active = true;
  return effect;
}

组件在初始化渲染的时候会创建一个effect,和Watcher会传入一个函数。当这个effect被执行的时候会存入全局栈,初次创建effect的时候会判断当前栈是否存在当前effect。文中的activeEffectVue2.0中的Dep.target是一个意思,都是指的当前执行的作用域。

依赖收集Track

csharp 复制代码
// reactivity/effect.js

let targetMap = new WeakMap(); // 存储effect的一种结构
export function Track(target, type, key) { // target:目标对象 key: 对象中的值
  if (!activeEffect) return
  let depMap = targetMap.get(target);
  if (!depMap) {
    targetMap.set(target, (depMap = new Map()))
  }
  let dep = depMap.get(key);
  if (!dep) {
    depMap.set(key, (dep = new Set()))
  }
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect); // 收集effect
  }
}

Vue3.0的依赖收集和Vue2.0的有所区别,vue2中是利用闭包对每个属性都创建了一个dep来进行依赖收集。而在Vue3中是创建了一种特殊的存储结构:目标对象 --> 对象属性key --> effect数组。

在GET处收集

javascript 复制代码
// reactivity/baseHandlers.js

function createGetter(isReadonly = false, shallow = false) {
  return function get(target, key, receiver) {
    /*
    *******
    *******此处省略
    */
    if (!isReadonly) {
      // 收集依赖
      Track(target, TrackOpTypes.GET, key); // +++++++++++++++++
    }
    return res;
  }
}

Proxy中,如果不是只读就对当前属性进行依赖收集。

派发更新trigger

scss 复制代码
// reactivity/baseHandlers.js

// 触发更新
export function trigger(target, type, key, newValue, oldValue) {
  const depsMap = targetMap.get(target); // 获取当前对象的属性集合
  if (!depsMap) return;
  let effects = new Set(); // 需要执行的effect执行列
  const add = (effectAdd) => { // 把effect添加到effect执行列中
    if (effectAdd) {
      effectAdd.forEach(effect => {
        effects.add(effect);
      })
    }
  }
  add(depsMap.get(key)); // 根据属性获取当前属性的effect列
  if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => { // 如果修改的是数组的长度,这里需要做特殊处理
      if (key === 'length' || key >= newValue) {
        add(dep)
      }
    })
  } else {
    if (key !== undefined) {
      add(depsMap.get(key));
    }
    switch (type) {
      case TriggerOpTypes.ADD: {
        if (isArray(target) && isIntergetKey(key)) {
          add(depsMap.get('length'))
        }
        break;
      }
    }
  }
  effects.forEach(effect => { // 执行effect
    if (effect.options.scheduler) { // 在computed和watch中会执行
      effect.options.scheduler(effect);
    } else {
      effect();
    }
  })
}

trigger() 函数,首先获取当前 targetMapdata 对应的主题对象的 depsMap,而这个 depsMap 即我们在依赖收集时在 track 中定义的。

测试

xml 复制代码
<body>
  <div id="app"></div>
  <button id="updateButton">改变</button>
</body>
ini 复制代码
let state = reactive({
  age: 22,
  name: '小明'
});

const el = document.getElementById('app')

effect(() => { // 模拟页面使用响应式数据
  el.innerHTML = state.age;
})

document.getElementById('updateButton').onclick = function() {
  state.age++;
}

我们使用effect来模拟我们在页面使用reactive的响应式数据,因为目前还没有实现模版编译以及渲染的代码,发现我们点击按钮页面会一直更新。

小结

对比Vue2.0通过Object.defineProperty在数据定义时为指定具体属性添加 getter/setter 拦截、为每个对象添加deps依赖数组的响应式系统实现方案。Vue3.0采用Proxy结合WeakMap、WeakSet,通过代理在运行时拦截对象的基本操作来收集依赖并全局管理,在性能上就得到了很大的提升,也为开发者带来了更好的开发体验。

如果觉得本文有帮助 记得点赞三连哦 十分感谢!

vue2.0 和 vue3.0系列文章(后续更新)

相关推荐
崔庆才丨静觅29 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax