Skip to main content

computed 计算属性原理

一、computed 用法

computed() 接收一个 getter 函数,自动追踪响应式依赖,返回值为一个计算属性 ref,可以通过 .value 访问计算结果,在模板中会自动解包。

1、基本用法

<template>
<input type="text" v-model="txt" />
<div>txt length is {{ length }}</div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const txt = ref('')
const length = computed(() => txt.value.length)
</script>

注意,追踪的是响应式依赖返回值才会随依赖的更新而更新。另外,computed 函数中不应该有任何副作用,而且返回结果是只读的。

2、可写的 computed

如果需要用到可写的 computed,可以用另一种写法:

<template>
<input type="number" v-model="num" />
<div>double num is {{ doubleNum }}</div>
<button @click="changeComputed">change computed to 10</button>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const num = ref(1)
const doubleNum = computed({
// getter
get() {
return num.value * 2
},
// setter
set(newNum) {
num.value = newNum / 2
}
})

const changeComputed = () => {
doubleNum.value = 10
}
</script>

上面提高 changeComputed 改变 computed 的返回值,此时 computed 是可写的。

二、computed 原理

computed 本质是一个惰性求值的观察者,内部根据参数的写法,兼容创建的 getter 和 setter,然后创建一个 ref 实例,在实例中通过 dirty 变量标记是否需要重新求值,根据依赖状态的变化完成 computed 值的计算,最后返回创建的 ref 实例。

computed.ts
// https://github.com/vuejs/vue/blob/main/src/v3/reactivity/computed.ts#L37

export function computed<T>(
// 可传入 getter 函数或包含 get、set 的对象
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
// 依赖收集和触发依赖的钩子函数,只在开发环境中起作用
debugOptions?: DebuggerOptions
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>

// 判断传入参数是否是一个函数
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
// 如果 getterOrOptions 是个函数
// 说明返回结果是不可写的,所以 setter 会设为空函数
getter = getterOrOptions
setter = __DEV__
? () => {
warn('Write operation failed: computed value is readonly')
}
: noop
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}

const watcher = isServerRendering()
? null
: new Watcher(currentInstance, getter, noop, { lazy: true })

if (__DEV__ && watcher && debugOptions) {
watcher.onTrack = debugOptions.onTrack
watcher.onTrigger = debugOptions.onTrigger
}

// 创建一个 ref 实例
const ref = {
// some libs rely on the presence effect for checking computed refs
// from normal refs, but the implementation doesn't matter
effect: watcher,
get value() {
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
if (__DEV__ && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
}
watcher.depend()
}
return watcher.value
} else {
return getter()
}
},
set value(newVal) {
setter(newVal)
}
} as any

def(ref, RefFlag, true)
def(ref, ReactiveFlags.IS_READONLY, onlyGetter)

// 返回 ref 实例
return ref
}