vue3响应式核心 reactivity 源码逻辑分析

22 篇文章 0 订阅
9 篇文章 0 订阅
文章介绍了Vue3中响应式系统的实现原理,通过Proxy在get时收集依赖,在set时触发更新。核心概念包括ReactiveEffect类,它在运行时收集依赖,并通过track和trigger函数管理依赖关系。当响应式数据变更时,会触发已收集的effect进行更新,实现数据驱动视图的更新。
摘要由CSDN通过智能技术生成

阅读此文,首先要知道大概知道一些vue响应式系统(点击跳转官网链接)

相关链接:

起步

const state = reactive({a: 1})
effect(() => {
  document.body.innerHTML = state.a
});

首先举个超级简单的例子,当js执行后,页面会显示出a的值“1”,当我们为state.a赋值时,页面也会根据赋值的不同而改变,本文的目的就是理解这块代码的实现原理

第一步

读本篇博客的小伙伴们应该有了解,Vue3中使用了Proxy来创建响应式对象(暂不考虑ref),在get时收集依赖,set时触发依赖更新,最终暴露出reactive等常用的api

首先上一段官网上的代码片段来简单理解一下reactive的实现逻辑

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key) // 收集依赖
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key) // 触发依赖
      Reflect.set(target, key, value, receiver)
    }
  })
}

代码解释:当创建reactive后,会对传入对象进行proxy代理,在被订阅者使用(get)时,触发track函数 收集依赖,记录下“订阅者在这里用到当前reactive啦!”,在对象被修改(set)时,触发trigger函数 触发依赖,“要去更新所有用到当前reactive订阅者!”

那么这个订阅者是什么呢?

订阅者可以是一个组件、computed、watch、effect、watchEffect…
但这些东西的核心都是ReactiveEffect

第二步

ReactiveEffect

class ReactiveEffect主要是用来收集依赖的,在其实例.run时,会将当前实例放在全局activeEffect上,然后调用传入的函数(比如组件的render函数/effect、computed、watch的回调函数),在调用的过程中响应式数据->get触发执行track函数收集依赖,就可以将“当前effect和响应式数据关联起来”啦

// 省略computed等一系列逻辑

let activeEffect; // 全局变量

class ReactiveEffect {
  constructor(fn,scheduler) {...} // 此处只关注该流程的简单实现,忽略scheduler相关内容
  run(){
    try {
      this.parent = activeEffect // 兼容嵌套关系(可忽略)
      activeEffect = this // 将当前实例放在全局变量activeEffect上
      return this.fn() // 调用传入的函数
    } finally {
      // 恢复activeEffect
      activeEffect = this.parent
      this.parent = undefined
    }
  }
}

ReactiveEffect

小总结

到这里,整体的流程已经有眉目了,我们以目前理解的概念 举例重新梳理一下

首先执行代码时,effect函数会创建new ReactiveEffect(fn),然后会执行.run(),也就是会执行例子中传入的函数方法,在执行的过程中用到响应式数据state.a会通过reactive -> get触发track收集依赖activeEffect,后续更新时(state.a = 2),reactive -> set触发后,会调用trigger触发依赖进行更新

然后让我们继续往下看,track收集依赖 和 trigger触发依赖都是什么?

第三步
track 依赖收集

targetMap

在说track之前我们必须要了解一个全局变量targetMap,他是一个weakMap对象(key、value都是引用类型),所有响应式数据和effect对应的关系都会被储存在这个对象中
内部数据储存的结构是:key=响应式对象value=Map对象,这个Map对象被命名为depsMap
depsMap存储的是响应式对象的属性和effect的对应关系,结构为key=响应式对象中的属性名,value=Set对象
Set对象被命名为dep中存储的就是对应的effect

targetMap

让我们说回track方法

对照上边的图,track方法会将全局变量activeEffect与当前响应式收集起来,除了放在targetMap中,也会在当前activeEffect.deps数组中存储图中的dep(Set格式)对象,达到双向收集的效果,也用于清依赖时使用

/**
 * @param {Object} target 响应式对象
 * @param {String} key 响应式对象中使用到的属性
 */
function track(target, key) {
  if (activeEffect) { // 全局effect为空的话就什么都不做
    // 1.获取并判断targetMap有没有当前响应式对象,没有就创建一个
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    // 2.获取并判断当前depsMap中有没有响应式对象中的当前“key”属性,没有就调createDeo创建
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }
    // 3.继续向dep对象中放值,同时将dep存入当前effect.deps数组中去
    trackEffects(dep)
  }
}
// 3.继续向dep对象中放值,同时将dep存入当前effect.deps数组中去
function trackEffects(dep) {
    dep.add(activeEffect);
    activeEffect && activeEffect.deps.push(dep);
}
trigger 触发依赖

触发依赖就是通过传入的targetkey,来找到并执行 响应式对象.key的所有已被收集effect,来达到触发更新的目的(也就是执行对应effect的所有run方法)

function trigger(target, key) {
  const depsMap = targetMap.get(target); // 获取到depsMap中的
  if (!depsMap) {return}
  // 本次代码忽略对clear、数组等处理,只关注对象
  const dep = depsMap.get(key); // 当前响应式对象.属性 需要响应的所有effect(Set格式)
  const effect = [...dep]
  triggerEffects(dep); // 执行effect
}
// 执行effect
triggerEffects(effects) {
  for (const effect of effects) {
      effect.run()
  }
}
到此位置,整个响应式的大概流程就已经明确了!
const state = reactive({a: 1})
effect(() => {
  document.body.innerHTML = state.a
});

还是开头时的那个例子,首先执行代码时,会执行effecteffect函数会创建new ReactiveEffect(fn),然后会执行.run(),在.run()中执行fn的过程中用到响应式数据state.a会通过reactive -> get触发track,为activeEffect.depstargetMap (weakMap对象)相互收集依赖;
后续更新响应式数据state.a = 2时,reactive -> set触发后,会调用trigger通知所有的订阅者ReactiveEffect更新,ReactiveEffect就会重新走.run方法,执行fn函数重新渲染页面/收集依赖

完整流程


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值