带你一行一行手写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系列文章(后续更新)

相关推荐
开心工作室_kaic9 分钟前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿29 分钟前
webWorker基本用法
前端·javascript·vue.js
cy玩具1 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
清灵xmf1 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据1 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
334554322 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理