Vue3源码阅读笔记【reactive和effect的理解和实现】

Vue3源码中的reactive和effect的理解和实现

在Vue3中,reactive和effect是两个非常重要的API,用于实现响应式数据和副作用函数。本文将介绍它们的基本用法,以及简单的实现原理。

文章目录

  • Vue3源码中的reactive和effect的理解和实现
    • 深入reactive
    • 深入effect
    • 依赖收集track和依赖触发trigger
      • track的简单实现
      • trigger的简单实现
    • 关于响应式依赖的思考
    • 总结

由于源码中逻辑分支较多,代码示例大多都是采用简单实现,帮助理解

深入reactive

reactive是一个工厂函数,用于创建响应式对象。使用reactive函数创建的对象,其属性可以被自动追踪,当属性发生变化时,会自动更新相关的视图。

下面是reactive函数的简单实现:

function reactive(obj) {
  return new Proxy(obj, {
	  get(target, key) {
	    track(target, key);  // 追踪属性访问
	    return target[key];
	  },
	  set(target, key, value) {
	    target[key] = value;
	    trigger(target, key);  // 触发更新
	  },
  };);
}

上述代码中,使用Proxy对象实现了对目标对象的代理。当访问代理对象的属性时,会自动调用get方法;当设置代理对象的属性时,会自动调用set方法。

在get方法中,我们调用了track函数,用于追踪属性的访问。track函数的实现可以参考Vue3源码中的effect.ts文件,不在本文讨论范围内。

在set方法中,我们先更新了目标对象的属性值,然后调用了trigger函数,用于触发更新。trigger函数的实现也可以参考Vue3源码中的effect.ts文件。

深入effect

effect是一个函数,用于创建副作用函数。使用effect函数创建的副作用函数,可以自动追踪其内部响应式数据的变化,当数据变化时,会自动重新执行副作用函数。

下面是effect函数的简单实现:

class reactiveEffect {
    private _fn: any
    constructor(fn: Function) {
        this._fn = fn
    }
    run(){
        activeEffect = this
        this._fn()
    }
}


let activeEffect 
export function effect(fn) {
    const _effect = new reactiveEffect(fn)
	activeEffect = _effect 
	// 在这里我们将当前effect赋值到全局属性,当我们调用传入的副作用函数时,也就会访问响应式数据,这时在get方法中收集当前effect,就完成了effect的依赖收集。
    _effect.run()
    activeEffect = null;
}
function effect(fn) {
  const runner = () => {
    fn();
  };
  runner();
  
  return runner;
}

上述代码中,我们创建了一个名为runner的函数,用于执行副作用函数。在创建runner函数后,我们立即执行了一次副作用函数,用于初始化副作用函数的状态。

在副作用函数中访问的响应式数据,会被track函数自动追踪。当响应式数据发生变化时,trigger函数会自动调用runner函数,重新执行副作用函数。

依赖收集track和依赖触发trigger

track的简单实现

const targetMap = new WeakMap() // 用于存储所有的目标对象(即响应式对象)以及它们对应的 depsMap
export function track(target, key) {
    let depsMap = targetMap.get(target) //用于存储目标对象的所有响应式属性以及它们对应的依赖列表 dep。
    if(!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }

    let dep = depsMap.get(key)// dep 是一个 Set 对象,用于存储所有依赖于某个响应式属性的 effect 函数
    if(!dep) {
        dep = new Set()
    }
    dep.push(activeEffect.run) // 将当前effect收集到dep中
}

trigger的简单实现

function trigger(target, key = null) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }

  const effects = new Set()

  // 收集所有与 target[key] 相关的 effect
  const addEffects = (dep) => {
    dep.forEach((effect) => {
      effects.add(effect)
    })
  }

  if (key !== null) {
    const dep = depsMap.get(key)
    if (dep) {
      addEffects(dep)
    }
  } else {
  	// 如果不指定 key,则会遍历 depsMap 中所有的 dep,收集它们中的 effect 并执行。
    depsMap.forEach(addEffects)
  }

  // 执行所有相关的 effect
  effects.forEach((effect) => {
    effect()
  })
}

关于响应式依赖的思考

从以上可以看出,当我们显式调用effect时,很容易就能将副作用函数收集为当前依赖,而在模版内的响应式数据应该如何收集依赖呢

简单来说,Vue 的模板编译器会将模板解析成抽象语法树(AST),然后生成渲染函数。在生成渲染函数时,模板中的数据绑定和指令会被编译成 JavaScript 代码,并将其包装在 render 函数中。render 函数是一个纯函数,它接收一个上下文对象作为参数,返回一个 VNode 对象。在执行 render 函数期间,访问响应式数据的属性会自动触发依赖追踪,将当前正在执行的 render 函数添加到该属性对应的 dep 中。

因此,虽然在 Vue 模板中的响应式数据没有明显的 effect 函数,但是它们的响应式更新机制和通过 effect 函数实现的响应式数据是类似的,都是通过依赖追踪和响应式更新实现的。

总结

reactive和effect是Vue3中实现响应式数据和副作用函数的核心API,学习并理解这部分内容对于深入Vue核心逻辑是很有帮助

你可能感兴趣的:(前端,js,vue,javascript,vue.js)