写点什么

前端培训:Vue3 计算属性比普通函数好的原因

作者:@零度
  • 2022 年 2 月 23 日
  • 本文字数:2123 字

    阅读完需:约 7 分钟

写在前面

计算属性是 Vue 开发中一个非常实用的 API,它允许用户自定义一个计算方法,然后根据一些依赖的响应式数据计算出新值并返回。当依赖发生变化时,计算属性会自动重新计算获取新值,使用方便。我们看出计算属性本质上是对依赖的计算,为什么不直接使用函数呢?在 Vue3 中的计算属性又是如何实现的呢?



计算属性 computed

我们先简单看个例子,我们看到再设置了计算属性 addOne 后,前端培训直接改变 addOne.value 的值会报错,只能通过改变原始值 count.value 才不会报错。这是因为:

  • 如果传递给 computed 的是一个函数,那就是一个 getter 函数,只能获取它的值,而不能直接修改它

  • 在 getter 函数中,根据响应式对象重新计算出新值,叫做计算属性,这个响应式对象叫做计算属性的依赖

const count = ref(1);

const addOne = computed(()=>count.value+1);

console.log(addOne.value);//2

addOne.value++;//error

count.value++;

console.log(count.value);//3

那么,我们应该如何修改 addOne.value 值呢?那就是在 computed 中设置 set 函数,进行自定义修改值。

const count = ref(1);

const addOne = computed({

get:()=>count.value+1,

set:val=>count.value=val-1

});

addOne.value = 1;

console.log(count.value);//0

我们研究源码:

export function computed<T>(

getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>

) {

let getter: ComputedGetter<T>

let setter: ComputedSetter<T>

// 如果传入是 function 说明是只读 computed

if (isFunction(getterOrOptions)) {

getter = getterOrOptions

setter = __DEV__

? () => {

console.warn('Write operation failed: computed value is readonly')

}

: NOOP

} else {

// 不是方法说明是自定义的 getter setter

getter = getterOrOptions.get

setter = getterOrOptions.set

}

let dirty = true

let value: T

let computed: ComputedRef<T>

// 创建 effect, 我们在看 effect 源码时知道了传入 lazy 代表不会立即执行,computed 表明 computed 上游依赖改变的时候,会优先 trigger runner effect, scheduler 表示 effect trigger 的时候会调用 scheduler 而不是直接调用 effect

const runner = effect(getter, {

lazy: true,

// mark effect as computed so that it gets priority during trigger

computed: true,

scheduler: () => {

// 在触发更新时把 dirty 置为 true, 不会立即更新

if (!dirty) {

dirty = true

trigger(computed, TriggerOpTypes.SET, 'value')

}

}

})

// 构造一个 computed 返回

computed = {

__v_isRef: true,

// expose effect so computed can be stopped

effect: runner,

get value() {

// dirty 为 ture, get 操作时,执行 effect 获取最新值

//

if (dirty) {

value = runner()

dirty = false

}

// dirty 为 false, 表示值未更新,直接返回

track(computed, TrackOpTypes.GET, 'value')

return value

},

set value(newValue: T) {

setter(newValue)

}

} as any

return computed

}

computed 计算属性有两个特点:

  • 延时计算:只有当我们访问计算属性时,真正运行 computed getter 函数计算

  • 缓存:它的内部会缓存上次的计算结果 value,而只有 dirty 为 true 时才会重新计算,如果访问计算属性时 dirty 为 false,那么直接返回这个 value

那么,计算属性的优势是:只要依赖不变化,就可以使用缓存的 value 而不用每次再渲染组件的时候都执行函数去计算。

做个嵌套计算的小例子,我们看到:对于 addOne 而言,web前端培训它收集的依赖是组件副作用渲染函数,而对于 count 而言,它收集的依赖是 addTwo 内部的 runner 函数。当我们修改 count 值,会进行派发通知,先运行 addOne 中的 setter 函数,此时 addOne 中的 dirty 值变成 true,然后 trigger 函数再次派发通知;接着运行 addTwo 中的 setter 函数,此时把 addTwo 中的 dirty 值设置为 true;当我们再次访问 addTwo 中的值时,发现 dirty 值为 true,就会执行 addTwo 的 computed 函数,会先去执行 addOne.value + 1,再去执行 addOne 的 computed 函数中的 count.value + 1。这样就得到最后打印出来的值为 2。

const count = ref(0);

const addOne = computed(()=>{

return count.value + 1;//1

})

const addTwo = computed(()=>{

return addOne.value + 1;//2

})

console.log(addTwo.value);//2

得益于 computed 计算属性的巧妙设计,无论嵌套多少层都能够正常运行。

import {ref,computed} from "vue";

import {effect} from "@vue/reactivity";

const count = ref(0);

const addOne = computed(()=>{

return count.value + 1;

})

effect(()=>console.log(addOne.value+count.value))

function add(){

count.value++;

}

add();

我们看到上面代码最终输出结果是:1 3 3

当我们第一次执行 addOne 的 computed 计算属性时,count.value 值还是 0,而 addOne.value 的值为 1,将会触发并执行 effect,此时打印出来还是 1。而后执行 add()函数,会进行修改 count.value 的值,会触发并执行 effect 函数,因为 addOne 也是 effect 的依赖,addOne 的 runners 函数也是 count.value 的依赖,count.value 值的修改会执行 runners 函数,会再次执行 addOne 的依赖,接着会触发 effect 函数,因此会输出两次 3。

computed 函数返回的对象实际上劫持的是 value 属性的 getter 和 setter,但是为什么我们在组件的模板中访问一个计算属性变量,不用手动在后面加.value 呢?

用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
前端培训:Vue3计算属性比普通函数好的原因