计算属性
计算属性 API: computed
例子
import { computed, ref } from 'vue'
const count = ref(1)
const result = computed(() => count.value + 1)
console.log(result.value) // 2
result.value++ // error
count.value++
console.log(result.value) // 3
- 先使用
ref API
创建了一个响应式对象count
- 再使用
computed API
创建了另一个响应式对象result
- 修改
count.value
的时候,result.value
就会自动发生变化 - 直接修改
result.value
会报一个错误- 因为如果我们传递给
computed
的是一个函数,那么这就是一个getter
函数,我们只能获取它的值,而不能直接修改它 - 也可以给
computed
传入一个对象,达到修改的目的
- 因为如果我们传递给
const result = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
computed API 的实现
function computed(getterOrOptions) {
// getter 函数
let getter
// setter 函数
let setter
// 标准化参数
if (isFunction(getterOrOptions)) {
// 表面传入的是 getter 函数,不能修改计算属性的值
getter = getterOrOptions
setter = (process.env.NODE_ENV !== 'production')
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
}
else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 数据是否脏的
let dirty = true
// 计算结果
let value
let computed
// 创建副作用函数
const runner = effect(getter, {
// 延时执行
lazy: true,
// 标记这是一个 computed effect 用于在 trigger 阶段的优先级排序
computed: true,
// 调度执行的实现
scheduler: () => {
if (!dirty) {
dirty = true
// 派发通知,通知运行访问该计算属性的 activeEffect
trigger(computed, "set" /* SET */, 'value')
}
}
})
// 创建 computed 对象
computed = {
__v_isRef: true,
// 暴露 effect 对象以便计算属性可以停止计算
effect: runner,
get value() {
// 计算属性的 getter
if (dirty) {
// 只有数据为脏的时候才会重新计算
value = runner()
dirty = false
}
// 依赖收集,收集运行访问该计算属性的 activeEffect
track(computed, "get" /* GET */, 'value')
return value
},
set value(newValue) {
// 计算属性的 setter
setter(newValue)
}
}
return computed
}
- 主要做了三件事情
- 1、标准化参数
-
computed
函数接受两种类型的参数- 一个是
getter
函数 - 一个是拥有
getter
和setter
函数的对象 - 通过判断参数的类型,我们初始化了函数内部定义的
getter
和setter
函数
- 一个是
-
- 2、创建副作用函数
runner
-
computed
内部通过effect
创建了一个副作用函数, - 它是对
getter
函数做的一层封装 - 创建时第二个参数为
effect
函数的配置对象-
lazy
为true
: 表示effect
函数返回的runner
并不会立即执行 -
computed
为true
:表示这是一个computed effect
,用于trigger
阶段的优先级排序 -
scheduler
:表示它的调度运行的方式
-
-
- 3、创建
computed
对象并返回- 拥有
getter
和setter
函数 - 当
computed
对象被访问的时候- 首先会触发
getter
- 然后会判断是否
dirty
- 如果是就执行
runner
,然后做依赖收集
- 首先会触发
- 当直接设置
computed
对象时- 会触发
setter
,即执行computed
函数内部定义的setter
函数
- 会触发
- 拥有
计算属性的运行机制
- 注意
-
computed
内部两个重要的变量 - 第一个
dirty
表示一个计算属性的值是否是“脏的”,用来判断需不需要重新计算 - 第二个
value
表示计算属性每次计算后的结果
-
- 1、当渲染阶段访问到计算属性,则触发了计算属性的
getter
函数
get value() {
// 计算属性的 getter
if (dirty) {
// 只有数据为脏的时候才会重新计算
value = runner()
dirty = false
}
// 依赖收集,收集运行访问该计算属性的 activeEffect
track(computed, "get" /* GET */, 'value')
return value
}
- 由于默认
dirty
是true
,所以这个时候会执行runner
函数,并进一步执行computed getter
- 如果访问到了响应式对象,所以就会触发响应式对象的依赖收集过程
- 由于是在
runner
执行的时候访问到了响应式对象,所以这个时候的activeEffect
是runner
函数 -
runner
函数执行完毕,会把dirty
设置为false
- 然后执行
track(computed,"get",'value')
函数做依赖收集 - 这个时候
runner
已经执行完了,所以activeEffect
是组件副作用渲染函数 - 注意:这是两个依赖收集过程
- 对于计算属性来说,它收集的依赖是组件副作用渲染函数
- 对于计算属性中访问的响应式对象来说,它收集的依赖是计算属性内部的
runner
函数
- 当修改计算属性时,会派发通知,此时这里不是直接调用
runner
函数,而是把runner
作为参数去执行scheduler
函数 -
trigger
函数内部对于effect
函数的执行方式如下:
const run = (effect) => {
// 调度执行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
// 直接运行
effect()
}
}
-
computed API
内部创建副作用函数时,已经配置了scheduler
函数
scheduler: () => {
if (!dirty) {
dirty = true
// 派发通知,通知运行访问该计算属性的 activeEffect
trigger(computed, "set" /* SET */, 'value')
}
}
- 并没有对计算属性求新值,而仅仅是把
dirty
设置为true
- 再执行
trigger(computed, "set" , 'value')
,去通知执行计算属性依赖的组件渲染副作用函数,即触发组件的重新渲染 - 在组件重新渲染的时候,会再次访问 计算属性,我们发现这个时候
dirty
为true
,然后会再次执行computed getter
image.png
Computed 计算属性两个特点
- 1、延时计算
- 只有当我们访问计算属性的时候,它才会真正运行
computed getter
函数计算
- 只有当我们访问计算属性的时候,它才会真正运行
- 2、缓存
- 它的内部会缓存上次的计算结果
value
,而且只有dirty
为true
时才会重新计算 - 如果访问计算属性时
dirty
为false
,那么直接返回这个value
- 它的内部会缓存上次的计算结果
Computed 的优势
只要依赖不变化,就可以使用缓存的
value
而不用每次在渲染组件的时候都执行函数去计算
嵌套计算属性
计算属性中访问另外一个计算属性
const count = ref(0)
const result1 = computed(() => {
return count.value + 1
})
const result2 = computed(() => {
return result1.value + 1
})
console.log(result2.value)
计算属性的执行顺序
- 计算属性创建
effect
时,标记了computed
标识的,用于trigger
阶段的优先级排序 -
trigger
函数执行effects
的过程
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || !shouldTrack) {
if (effect.options.computed) {
computedRunners.add(effect)
}
else {
effects.add(effect)
}
}
})
}
}
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
effect()
}
}
computedRunners.forEach(run)
effects.forEach(run)
- 在添加待运行的
effects
的时候,会判断每一个effect
是不是一个computed effect
- 如果是的话会添加到
computedRunners
中 - 在后面运行的时候会优先执行
computedRunners
- 然后再执行普通的
effects
为什么computed runner
执行优先于普通的effect
函数?
- 因为当修改响应式数据时,会触发关联的计算属性的
runner
和effect
执行 - 如果先调用普通
effect
,这时dirty
为false
,使用的数据仍然是上次的缓存数据,导致更新不及时
网友评论