专栏分享:vue2源码专栏vue3源码专栏vue router源码专栏玩具项目专栏,硬核推荐

欢迎各位ITer关注点赞收藏

语法

传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象

const count = ref(1)
const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // 错误!

或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态

const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
}) plusOne.value = 1
console.log(count.value) // 0

源码实现

  • @issue1 computed参数兼容只传getter方法和handler对象的情况
  • @issue2 缓存特性,只要依赖的变量值没有发生变化,就取缓存中的值

_dirty作为缓存标识,如果依赖的变量值有变化,则将 _dirty 值置为 true,后续读取计算属性时,重新执行getter;否则直接取_value

  • @issue3 嵌套effect,firstname -> 计算属性fullName -> effect,下一章节详细介绍
import { isFunction } from '@vue/shared'
import { ReactiveEffect, trackEffects, triggerEffects } from './effect' /**
* @issue1 computed参数兼容只传getter方法和handler对象
* @issue2 缓存,只要依赖的变量值没有发生变化,就取缓存中的值
* @issue3 嵌套effect,firname -> fullName -> effect
*/
class ComputedRefImpl {
public effect
public _dirty = true // 默认应该取值的时候进行计算
public _value
public dep = new Set()
public __v_isReadonly = true
public __v_isRef = true
constructor(public getter, public setter) {
// 我们将用户的getter放到effect中,这里面firstname和lastname就会被这个effect收集起来
this.effect = new ReactiveEffect(getter, () => {
// 稍后依赖的属性firstname、lastname变化了,会执行此调度函数
if (!this._dirty) {
this._dirty = true
// 实现一个触发更新 @issue3
triggerEffects(this.dep)
}
})
} // 类中的访问器属性 底层就是Object.defineProperty
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get
get value() {
// 做依赖收集 @issue3
trackEffects(this.dep)
// @issue2
if (this._dirty) {
// 说明这个值是脏的
this._dirty = false
this._value = this.effect.run()
}
return this._value
} set value(newValue) {
this.setter(newValue)
}
} export const computed = getterOrOptions => {
let onlyGetter = isFunction(getterOrOptions) let getter
let setter
// @issue1
if (onlyGetter) {
getter = getterOrOptions
setter = () => {
console.warn('no set')
}
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter)
}

trackEffects 和 triggerEffects 方法如下

export function trackEffects(dep) { // 收集dep 对应的effect
if (activeEffect) {
let shouldTrack = !dep.has(activeEffect) // 去重了
if (shouldTrack) {
dep.add(activeEffect)
// 存放的是属性对应的set
activeEffect.deps.push(dep) // 让effect记录住对应的dep, 稍后清理的时候会用到
}
}
} export function triggerEffects(effects) {
effects = new Set(effects);
for (const effect of effects) {
if (effect !== activeEffect) { // 如果effect不是当前正在运行的effect
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run(); // 重新执行一遍
}
}
}
}

嵌套 effect

让我们分析一下这个测试用例

const { effect, reactive, computed } = VueReactivity
const state = reactive({ firname: '李', lastname: '柏成' }) const fullName = computed(() => {
// defineProperty中的getter
return state.firstname + state.lastname
}) effect(() => {
app.innerHTML = fullName.value
}) setTimeout(() => {
state.firstname = '王'
}, 1000) // 1. firstname要依赖于计算属性的effect
// 2. 计算属性收集了外层effect
// 3. 依赖的值变化了会触发计算属性effect重新执行, 计算属性重新执行的时候会触发外层effect来执行 // computed 特点:缓存
console.log('fullName.value', fullName.value)
console.log('fullName.value', fullName.value)
  1. 当执行到 renderEffect 时,默认先执行一次 effect.run(),activeEffect --> renderEffect,并运行 this.fn() --> app.innerHTML = fullName.value
effect(() => {
app.innerHTML = fullName.value
})
  1. 当访问 fullName.value 时,在 getter 方法中执行 trackEffects(this.dep),计算属性fullName 依赖收集 当前的 activeEffect(renderEffect)
  2. 当运行 this._value = this.effect.run() 时,activeEffect --> computedEffect,并运行 this.fn() ---> return state.firstname + state.lastname
  3. 访问了state.firstname,属性 firstname 依赖收集当前的 activeEffect(computedEffect)
  4. 访问了state.lastname,属性 lastname 依赖收集当前的 activeEffect(computedEffect)
  5. 一秒钟后,firstname 发生了变化。。。firstname变化触发更新 triggerEffects --> computedEffect.scheduler()
  6. 在计算属性 scheduler 中,触发更新 triggerEffects(this.dep) --> renderEffect.run() ,最终重新渲染页面 app.innerHTML = fullName.value

【源码系列#03】Vue3计算属性原理(Computed)的更多相关文章

  1. 【Vue2.x源码系列06】计算属性computed原理

    上一章 Vue2异步更新和nextTick原理,我们介绍了 JavaScript 执行机制是什么?nextTick源码是如何实现的?以及Vue是如何异步更新渲染的? 本章目标 计算属性是如何实现的? ...

  2. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  3. 【Vue2.x源码系列07】监听器watch原理

    上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate.deep 内部实现 初始 ...

  4. 大白话Vue源码系列(03):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  5. Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)

    刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问 上一篇文章我们获取到了DefaultSqlSe ...

  6. Spring源码系列 — 注解原理

    前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...

  7. Typescript | Vue3源码系列

    TypeScript 是开源的,TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript.编译出来的 JavaScript 可以运行在任何浏览器上.TypeS ...

  8. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  9. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

  10. Ioc容器BeanPostProcessor-Spring 源码系列(3)

    Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...

随机推荐

  1. 系统内存管理:虚拟内存、内存分段与分页、页表缓存TLB以及Linux内存管理

    虚拟内存 虚拟内存是一种操作系统提供的机制,用于将每个进程分配的独立的虚拟地址空间映射到实际的物理内存地址空间上.通过使用虚拟内存,操作系统可以有效地解决多个应用程序直接操作物理内存可能引发的冲突问题 ...

  2. 十年磨一剑的华为云GES,高明在哪

    本文分享自华为云社区<华为云GES:十年磨一剑,打造业界一流的云原生分布式图数据库>,作者:GES图引擎服务小图 . 1.浅谈云原生图数据库 图数据库(graph database)是一个 ...

  3. PHP上传文件$_FILES, $_POST为空 empty 时, 文件上传大小限制

    原因 今天在使用ci upload库时, 上传mp4发现表单为空, 上传png等类型却可以正常. 折腾一番后才恍然, PHP上传大小限制的问题. Make一下. 真是失策啊, 一开始我还不相信到处瞎折 ...

  4. vscod 配置 morkdown 快捷键

    vscod 配置 morkdown 快捷键 1.首先在根目录添加.vscode 文件夹 ️1.1 新建一个 settings.json 文件 { "[markdown]": { & ...

  5. SpringBoot + 自定义注解,实现用户操作日志(支持SpEL表达式)

    背景 一个成熟的系统,都会针对一些关键的操作,去创建用户操作日志. 比如: XX人创建了一条订单,订单号:XXXXXXXXX 因为操作人或者订单号是动态的,所以有些开发人员,不知道获取,就将这种操作日 ...

  6. 小知识:调整OCI实例的时区

    之前在随笔中<Linux (RHEL)修改时区> 介绍了时区修改方法. 默认OCI实例中,时区是GMT,在国内用看着这个时区就是很别扭的事情,于是修改时区,实测无需配置 /etc/sysc ...

  7. 斜率优化DP 学习笔记

    斜率优化 DP 适用情况 适用于求解最优解(最大.最小)问题. 上凸壳与下凸壳 求解步骤 对于任意状态转义方程,设 \(A_i\),\(B_i\),使状态转移方程转化为 \(f_i = \min(f_ ...

  8. Oracle-降低表的高水位线

    在应用中存在一系列的表,对表的操作是批量插入又批量删除,最终导致表的水位线很高.高水位线影响全索引扫描的SQL.即影响系统的性能. 现有方法降低表的水位线: 1.降低表的高水位线 select 'al ...

  9. Hadoop集群模式安装笔记

    前言 Hadoop集群=HDFS集群+YARN集群 特点:两个集群逻辑上分离,通常物理上在一起:并且都是标准的主从架构集群 Hadoop安装 方式一源码编译安装 方式二官方编译安装包 () 环境 Ce ...

  10. DESTOON做中英双语言(多语言)切换版本具体详解

    第一次发原创好激动,该注意点什么? 在开发过程中用户有许多要求,比如这个多语言切换就是一个需求. 首先讲解一下DESTOON(DT)后台系统如何做这个中英.甚至多语言切换的这个功能. DT本身不自带多 ...