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)
}