当前位置: 首页 > news >正文

vue3源码学习(三)computed 源码学习

vue3源码学习(三) —— computed 源码学习

computed源码学习回顾、复习。


computed 源码学习

  • vue3源码学习(三) —— computed 源码学习
  • 一、实现 computed 基础函数
      • 1、定义computed模型
      • 2、实现 computed 函数
  • 二、computed 扩展
      • 1、收集及触发函数
            • 提炼出公共代码如下
      • 2、改造ComputedRefImpl加入依赖收集及触发
  • 三、使用及运行解读
  • 四、完整代码


vue3.x使用示例:

const state = reactive({name: 'zhangsan',age: 32
})
// 使用方式1
let aliasName = computed(()=> "*" + state.name)// 使用方式2
let aliasName2 = computed({get(){...},set(){...}
})effect(()=> document.getElementById('app').innerText = aliasName.value)

从根本上计算属性也是一个effect
在computed中使用响应数据时,会触发proxy的get函数
计算属性的目的是根据状态衍生属性,我们希望这个属性会有缓存功能,如果依赖的数据不变就不会重新计算
计算属性返回的是一个xxx.value的结果
计算属性可以在一个文件中多次重复使用
计算属性内部需要一个变量, 这个变量控制是否要重新执行 dirty,内部默认dirty是true 此时用户取值会执行此方法, 拿到返回结果返回并缓存起来, 将dirty变味false再次取值则dirty为false就去拿缓存的结果了如果依赖的值变化了, 会再次更新drity变为true, 再取值的时候就会执行,拿到新值

一、实现 computed 基础函数

1、定义computed模型

缓存、get、set、drity、effect、dep

import { activeEffect, ReactiveEffect, trackEffect, triggerEffect } from './effect'
class ComputedRefImpl{public dep = undefined;public _value; // 默认的缓存结果public __v_isRef = true;// 有这个属性意味着需要用.value来取值public _drity = true;public effect = undefined;constructor(public getter, pubilc setter){// 这里源码中不能使用effect(()=>{}) 这样函数会立即执行// 这里的第二个参数就是ReactiveEffect中的第二个参数schedulerthis.effect = new ReactiveEffect(getter, ()=>{// 当响应式对象变化时会调用这里的调度器,然后再取值的时候会重新计算this._drity = true})}// 类的属性访问器  等于Object.defineProperty(实例, value, {get})// 计算属性的值是通过访问器来取值的,当访问value时会调用get方法get value () {if(this._drity){// 取值才执行,并且将取到的缓存下来this._value = this.effect.run()this._drity = falue  // 表示取过值了}return this._value }set value (newVal) {this.setter(newVal)}
}

2、实现 computed 函数

分析:计算属性的目的是根据状态衍生属性,我们希望这个属性会有缓存功能,如果依赖的数据不变就不会重新计算,计算属性返回的是一个xxx.value的结果

function isFunciton (value) {return typeof value === 'function'
}
function noop = () => {}
export function computed(getterOrOption){let onlyGetter = isFunction(getterOrOption)let getter;let setter;if(onlyGetter){getter = getterOrOptionsetter = noop}else{getter = getterOrOption.getsetter = getterOrOption.set}// getter = 方法必须存在return new ComputedRefImpl(getter, setter)
}

二、computed 扩展

到这里一步简单的计算属性就完成了,这个时候有一个问题。如下代码setTimeout中改变响应式数据是否会触发执行计算属性?

    const state = reactive({name: 'zhangsan',age: 32,})// 计算属性的目的是根据状态衍生属性,我们希望这个属性会有缓存功能,如果依赖的数据不变就不会重新计算let aliasName = computed(()=> {console.log('默认不执行');return "*" + state.name})effect(()=> {document.getElementById('app').innerText = aliasName.value})setTimeout(() => {state.name = 'wangwu'}, 2000);

答案是不会执行,因为当state.name变化会触发proxy中的get,这个时候会触发到computed中的ReactiveEffect将_dirty标记为true。但是到这一步就断了,没有后续了,也没有地方去取值触发computed的get函数。因为这里是改变了state的值,所以我们可以使用effect中trigger函数中相同的逻辑使用dep去找到对应的effect去执行对应的run方法触发,从而重新执行computed传入的函数触发computed的get

1、收集及触发函数

computed中的收集和触发和effect中的收集触发一致,区别点在于computed只有dep这一级不需要target和key

提炼出公共代码如下
// 收集
function trackEffect(dep){// 将当前的effect添加到依赖收集let shouldTrack = dep.has(activeEffect)if(!shouldTrack){dep.add(activeEffect); // 添加当前的effect到依赖收集activeEffect.deps.push(dep); // 将当前的依赖收集添加到activeEffect的deps中}// 一个属性对应多个effect,一个effect对应多个属性; 多对多
}
// 触发
function triggerEffect(dep){const effects = [...dep];effects.forEach(effect => {// 当我重新执行此effect时,将当前的effect放到全局上 activeEffect 上//这里的activeEffect是正在执行的effect// 因为activeEffect重新赋值是在finally中执行的,try中执行完成后才会执行finally// 所以当在effct中改变属性时,activeEffect还是正在执行的effectif(activeEffect != effect) {if(effect.scheduler){effect.scheduler()}else{effect.run(); // 执行依赖收集中的effect}}});
}

2、改造ComputedRefImpl加入依赖收集及触发

class ComputedRefImpl{public dep = undefined;public _value; // 默认的缓存结果public __v_isRef = true;// 有这个属性意味着需要用.value来取值public _drity = true;public effect = undefined;constructor(public getter, pubilc setter){// 这里源码中不能使用effect(()=>{}) 这样函数会立即执行// 这里的第二个参数就是ReactiveEffect中的第二个参数schedulerthis.effect = new ReactiveEffect(getter, ()=>{// 当响应式对象变化时会调用这里的调度器,然后再取值的时候会重新计算this._drity = true// 新增代码triggerEffect(this.dep || (this.dep = new Set())) // 触发计算属性的依赖更新})}// 类的属性访问器  等于Object.defineProperty(实例, value, {get})// 计算属性的值是通过访问器来取值的,当访问value时会调用get方法get value () {// 新增代码if(activeEffect){// 如果有activeEffect,说明这个计算属性是在effect中取值,需要收集依赖// 需要让计算属性收集这个effect// 用户取值发生依赖收集trackEffect(this.dep)}if(this._drity){// 取值才执行,并且将取到的缓存下来this._value = this.effect.run()this._drity = falue  // 表示取过值了}return this._value }set value (newVal) {this.setter(newVal)}
}

三、使用及运行解读

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Reactivity Package</title>
</head>
<body><div id="app"></div><script type="module">import { reactive, effect, computed } from './reactivity.esm.js';const state = reactive({name: 'zhangsan',age: 32,})// 计算属性内部需要一个变量, 这个变量控制是否要重新执行  dirty// 内部默认dirty是true 此时用户取值会执行此方法, 拿到返回结果返回并缓存起来, 将dirty变味false// 再次取值则dirty为false就去拿缓存的结果了// 如果依赖的值变化了, 会再次更新drity变为true, 再取值的时候就会执行,拿到新值// 计算属性的目的是根据状态衍生属性,我们希望这个属性会有缓存功能,如果依赖的数据不变就不会重新计算let aliasName = computed(()=> {console.log('默认不执行');return "*" + state.name})effect(()=> {document.getElementById('app').innerText = aliasName.value})// 当我们取值时, 会调用此方法aliasName.value // 这个时候会触发computed// 当依赖的值发生变化后再取值会再次执行// vue3中计算属性也具备收集依赖的功能setTimeout(() => {// 这个setTimeout导致的执行顺序/*** 1. proxy 监听数据变化 set -> trigger* 2. trigger -> triggerEffects -> 找到依赖的effect* 3. 找到的effect执行 -> 触发计算属性的调度函数 -> triggerEffect* 4. 计算属性中的调度函数将计算属性对应的effect的dirty变为true再执行计算属性中的triggerEffect* 5. 触发computed中的get value 方法* 6. get value 方法中判断dirty是true, 重新执行用户传入的函数, 拿到最新结果返回并缓存起来, 将dirty变为false* 7. 计算属性的值发生变化触发effect更新视图*/state.name = 'wangwu'}, 2000);</script>
</html>

四、完整代码

import { isFunction } from "@vue/shared";
import { activeEffect, ReactiveEffect, trackEffect, triggerEffect } from './effect'// 从根本上计算属性也是一个effect
class ComputedRefImpl{// vue3中计算属性也具备收集依赖的功能public dep = undefined;public __v_isRef = true;// 有这个属性意味着需要用.value来取值public _dirty = true;public _value; // 默认的缓存结果public effect = undefinedconstructor(public getter, public setter){// 这里源码中不能使用effect(()=>{}) 这样函数会立即执行// 这里的第二个参数就是ReactiveEffect中的第二个参数schedulerthis.effect = new ReactiveEffect(getter, ()=>{console.log("computed scheduler");// 当响应式对象变化时会调用这里的调度器,然后再取值的时候会重新计算this._dirty = true // 标记为脏值triggerEffect(this.dep) // 触发计算属性的依赖更新})}// 类的属性访问器  等于Object.defineProperty(实例, value, {get})// 计算属性的值是通过访问器来取值的,当访问value时会调用get方法get value () {if(activeEffect){// 如果有activeEffect,说明这个计算属性是在effect中取值,需要收集依赖// 需要让计算属性收集这个effect// 用户取值发生依赖收集trackEffect(this.dep || (this.dep = new Set()))}if(this._dirty){// 取值才执行,并且将取到的缓存下来this._value = this.effect.run()this._dirty = false // 意味取过值了}return this._value}set value (newVal){this.setter(newVal)}
}const noop = () => {}
export function computed(getterOrOptions){let onlyGetter = isFunction(getterOrOptions)let getter;let setter;if(onlyGetter){getter = getterOrOptionssetter = noop}else{getter = getterOrOptions.getsetter = getterOrOptions.set}// getter = 方法必须存在return new ComputedRefImpl(getter, setter)
}

文章转载自:

http://w0X7heW6.kpsrc.cn
http://GjhM2Pcj.kpsrc.cn
http://DlsNqYgc.kpsrc.cn
http://u2ZgPHzh.kpsrc.cn
http://7ODhyTWs.kpsrc.cn
http://jGPJMHx5.kpsrc.cn
http://ikFt6Dda.kpsrc.cn
http://LSyBGvYz.kpsrc.cn
http://ut0dT2Yf.kpsrc.cn
http://s0qbUU6x.kpsrc.cn
http://tkZllApT.kpsrc.cn
http://ga4nxgjZ.kpsrc.cn
http://1kvDO9ts.kpsrc.cn
http://DgS06SON.kpsrc.cn
http://EmZiIIig.kpsrc.cn
http://Y8ADQXzj.kpsrc.cn
http://mM7TPGK4.kpsrc.cn
http://frJvlrEr.kpsrc.cn
http://M4xwkK2R.kpsrc.cn
http://TiscBN75.kpsrc.cn
http://Zni5Ac3U.kpsrc.cn
http://HoRik3e8.kpsrc.cn
http://SbFjgyh0.kpsrc.cn
http://wzQPROCg.kpsrc.cn
http://esMKe3LT.kpsrc.cn
http://jGixNH6E.kpsrc.cn
http://qSoiPIE2.kpsrc.cn
http://1dirA4IW.kpsrc.cn
http://Xzg7qJPa.kpsrc.cn
http://SlFW0WEv.kpsrc.cn
http://www.dtcms.com/a/379366.html

相关文章:

  • 94. 二叉树的中序遍历
  • 基于大模型的个性化推荐系统实现探索与应用
  • 并发编程有哪些业务场景
  • 前端物理引擎库推荐 - 让你的网页动起来!
  • 考华为认证可从事哪些工作?
  • 【Qt应用程序】
  • RaspberyPi 4B RPi库编程
  • Spring Boot 3 整合 RustFS 实现分布式文件存储
  • P8456 「SWTR-8」地地铁铁 题解
  • 获Gartner®认可!锐捷入选2025年Gartner园区网络基础设施管理与运营软件市场指南
  • 告别环境地狱!Java生态“AI原生”解决方案入驻 GitCode​
  • 【leetcode】322. 零钱兑换
  • 数据清洗:缺失值、异常值与重复数据处理全解析
  • 审计过程中常见的文档缺失问题如何避免
  • 图像投影(透视)变换
  • Spring Cloud Gateway:下一代API网关的深度解析与实战指南
  • springboot 启动流程及 ConfigurationClassPostProcessor解析
  • git中rebase和merge的区别
  • 66-python中的文件操作
  • 【PostgreSQL内核学习 —— (SeqScan算子)】
  • 资源图分配算法
  • SpringBoot 中单独一个类中运行main方法报错:找不到或无法加载主类
  • 2025全球VC均热板竞争格局与核心供应链分析
  • 用“折叠与展开”动态管理超长上下文:一种 Token 高效的外部存储操作机制
  • 深度解析指纹模块选型与落地实践
  • 从用户体验到交易闭环的全程保障!互联网行业可观测性体系建设白皮书发布
  • grafana启用未签名插件
  • MySQL 数据类型与运算符详解
  • 编程实战:类C语法的编译型脚本解释器(五)变量表
  • 原生js拖拽