超详细Vue3响应式原理介绍加源码阅读

本文详细介绍了Vue3的响应式原理,包括如何实现响应式、存储依赖、处理多个响应式对象,以及代理与反射的基础知识。通过源码阅读,探讨了Vue3中的track、trigger、ActiveEffect和Ref的运作方式,最后解答了关于Vue2与Vue3响应式差异的常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


published: true
date: 2022-2-3
tags: ‘前端框架 Vue’

Vue3 Reactivity

本章介绍Vue中另一个非常重要的模块,响应式。介绍了基本原理(含图)简单实现以及如何阅读源码。

致谢Vue Mastery非常好的课程,可以转载,但请声明源链接:文章源链接justin3go.com(有些latex公式某些平台不能渲染可查看这个网站)

Reactivity

作用

  • 学习Vue3的响应式原理;
  • 提高调试能力;
  • 使用响应式这个模块进行一些骚操作;
  • 为Vue做贡献

Vue是如何知道更新这些模板的呢?

image-20220123102005969

当price发生改变,Vue知道怎么更新这些模板。

image-20220123102240097

JavaScript并不会更新!

自己实现一个响应式原理

基本思路:当价格或者数量更新时,让它再跑一次;

image-20220123113221300
// effct这个函数就是让total重新计算一次;
let effect = () => {
   total  = price * quantity}  // 缩短上述中间代码
let dep = new Set()  // 去储藏effect,保证不会添加重复值
image-20220123113709364
  • 其中**track()**函数是添加这个数据;
  • 而**trigger()**函数<触发函数>会遍历我们存的每一个effect,然后运行它们;
image-20220123113835384

如何存储,让每个属性拥有自己的依赖

通常,我们的对象会有多个属性,每个属性都需要自己的dep(依赖关系),或者说effect的Set;

image-20220126142931144 image-20220126142943696
  • dep就是一个effect集(Set),这个effect集应该在值发生改变时重新运行;
image-20220126143323286
  • 要把这些dep储存起来,且方便我们以后再找到它们,我们需要创建有一个depsMap,它是一张存储了每个属性其dep对象的图;

  • 使用对象的属性名作为键,比如数量和价格,值就是一个dep(effects集)

  • 代码实现:

 const depsMap = new Map()
 
 function track(key) {
   
     // key值就是刚才的价格或者数量
     let dep = depsMap.get(key);  // 找到属性的依赖
     if (!dep){
     //如果没有,就创建一个
         depsMap.set(key, (dep = new Set()))
     }
     dep.add(effect)  // 添加effect,注意dep(Set)会去重
 }
 function trigger(key) {
   
     let dep = depsMap.get(key) // 找到键值的依赖
     if (dep) {
     // 如果存在,遍历并运行每个effect
         dep.forEach(effect => {
   
             effect()
         })
     }
 }
 // main
 let product = {
    price: 5, quantity: 2};
 let total = 0;
 let effect = () => {
   
     total = product.price * product.quantity;
 }
 // 存储effect
 track('quantity')
 effect()
  • 结果:
image-20220126144609965

如果我们有多个响应式对象呢?

image-20220126145012663

之前的是这样:

image-20220126145115743

这里depsMap是对每个属性进行存储,再上一层,我们需要对每个对象进行存储,其中每个对象包括了不同的属性,所以我们在这之上有用了一张表(Map),来存储每个对象;

image-20220126145414088

Vue3中这个图叫做targetMap,需要注意的是这个数据结构是WeakMap<就是键值为空时会被垃圾回收机制清除,就是响应式对象消失后,这里面存的相关依赖也会被自动清除>

const targetMap = new WeakMap();  // 为每个响应式对象存储依赖关系
// 然后track()函数就需要首先拿到targetMap的depsMap
function track(target, key) {
   
    let depsMap = targetMap.get(target);  // target是响应式对象的名称,key是对象中属性的名称
    if (!depsMap){
     // 不存在则为这个对象创建一个新的deps图
        targetMap.set(target, (depsMap = new Map())
    };
    let dep = depsMap.get(key);  // 获取属性的依赖对象,和之前的一致了
    if(!dep){
     //同样不存在就创建一个新的
        depsMap.set(key, (dep = new Set()))
    };
    dep.add(effect);
}
function trigger(target, key){
   
    const depsMap = targetMap.get(target) // 检查对象是否有依赖的属性
    if(!depsMap){
   return}  // 如果没有则立即返回
    let dep = depsMap.get(key)  // 检查属性是否有依赖的关系
    // 如果有的话将遍历dep,运行每个effect
    if (dep){
   
        dep.forEach(effect => {
   effect()})
    }
}
// main
let product = {
   price: 5, quantity: 2}
let total = 0
let effect = () => {
   
    total = product.price * product.quantity
}
track(product, "quantity")
effect()

运行:

image-20220126152655606

总结

响应式就是在每次更新值的时候重新计算一次,但是由于某些依赖什么什么的,某些对象对应的属性会导致另外的变量变化,所以需要一些数据结构去记录这些变化,就形成了下面的这些表(Map, Set)

image-20220126152722481

但目前我们还没有办法让我们的effect自动重新运行,这将在后续讲到;

代理与反射

引入

上一部分我们使用track()与trigger()显式构造了响应式引擎,这部分我们希望其自动跟踪和触发;

需求:

  • 访问了产品的属性或者说使用了get方法,就是我们想要调用track去保存effect的时候
  • 产品的属性改变或者说使用了set方法,就是我们想要调用trigger来运行那些保存了的effect

解决:

  • Vue2中,使用的是ES5中的Object.defineProperty()去拦截get或set;
  • Vue3中,使用的是ES6中的代理和反射去实现相同的效果;

代理与反射基础

通常我们会用三种方法获取某个对象中的属性:

  • let product = {
         price: 5, quantity: 2};
    product.quantity
    
  • product[quantity]
    
  • Reflect.get(product, ‘quantity’
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值