专栏分享: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)
- 当执行到 renderEffect 时,默认先执行一次 effect.run(),activeEffect –> renderEffect,并运行 this.fn() –>
app.innerHTML = fullName.value
effect(() => {
app.innerHTML = fullName.value
})
- 当访问 fullName.value 时,在 getter 方法中执行 trackEffects(this.dep),计算属性fullName 依赖收集 当前的 activeEffect(renderEffect)
- 当运行
this._value = this.effect.run()
时,activeEffect –> computedEffect,并运行 this.fn() —>return state.firstname + state.lastname
- 访问了state.firstname,属性 firstname 依赖收集当前的 activeEffect(computedEffect)
- 访问了state.lastname,属性 lastname 依赖收集当前的 activeEffect(computedEffect)
- 一秒钟后,firstname 发生了变化。。。firstname变化触发更新 triggerEffects –> computedEffect.scheduler()
- 在计算属性 scheduler 中,触发更新 triggerEffects(this.dep) –> renderEffect.run() ,最终重新渲染页面
app.innerHTML = fullName.value
1.本站内容仅供参考,不作为任何法律依据。用户在使用本站内容时,应自行判断其真实性、准确性和完整性,并承担相应风险。
2.本站部分内容来源于互联网,仅用于交流学习研究知识,若侵犯了您的合法权益,请及时邮件或站内私信与本站联系,我们将尽快予以处理。
3.本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
4.根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
5.本站是非经营性个人站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途
暂无评论内容