什么是computed
官方给出的解释:接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象
// 只读
function computed<T>(
getter: () => T,
debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>
// 可写的
function computed<T>(
options: {
get: () => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions
): Ref<T>
从代码上看这么解释毫无破绽,甚至于说人尽可知。但是很少有人知道computed
或者想了解其真正有用的几大特性
computed实用特性
1. 依赖追踪
import { reactive, computed } from 'vue'
const state = reactive({
s1: 1,
s2: 2
})
const info = computed(() => {
return state.a + state.b
})
我们定义了一个响应式数据state和一个计算属性info , Vue会自动追踪info 依赖的数据state.s1和state.s2,并建立相应的依赖关系。
也就是只有state.s1和state.s2发生变化的时候,info 才会重新计算,info 都将丝毫不受影响。
2. 缓存
还是上面的例子,如果state.s1和state.s2一直都不再改变值了,那么我们读取info 的时候,它将会返回上一次计算的结果,而不是重新计算。
3. 懒计算
这个特性比较容易被忽略,简单地说只有计算属性真正被使用(读取)的时候才会进行计算,否则咱就仅仅是定义了一个变量而已。
import { reactive, ref, toRefs , onMounted,watch,computed } from 'vue';
const state = reactive({
s1: 1,
s2: 2
})
const info = computed(() => {
console.log('初始执行计算')
return state.s1+ state.s2
})
const handleEdit=(type)=>{
console.log('step1', info.value)
state.s1 = 3
setTimeout(() => {
// 而是要等到再次读取的时候才会触发重新计算
console.log('step2', info.value)
}, 100)
}
了解完以上特性后我们回归到问题的本质,尝试构造一个符合以上特性的函数
1. 懒计算
依赖 effect
构造 (effect
注册的回调都是立刻执行)
const info = computed(() => {
console.log('初始执行计算')
return state.s1+ state.s2
})
//一个基础的effect
const infoEffect = effect(() => {
console.log('初始执行计算')// 立刻被打印
const back = state.s1+ state.s2
return back
})
const handleEdit=()=>{
console.log(infoEffect)
}
想要实现computed的懒执行,添加一个额外的参数lazy。它要实现的功能是:如果传递了lazy为true,副作用函数将不会立即执行,而是将执行的时机交还给用户,由用户决定啥时候执行
//一个基础的effect
const infoEffect = effect((fn, options = {}) => {
const effectFn = () => {
// ... 业务流
// 新增加的res存储fn执行的结果
const res = fn()
// ... 业务流,返回结果
return res
}
// 只有lazy不为true时才会立即执行
if (!options.lazy) {
effectFn()
}
//返回副作用函数让用户执行
return effectFn
})
改造一下
//一个基础的effect
const infoEffect = effect(() => {
console.log('初始执行计算')// 立刻被打印
const back = state.s1+ state.s2
return back
},
{
lazy: true
}
)
const handleEdit=()=>{
console.log(infoEffect())
}
2. 依赖追踪
function computed(getter){
const effectFn = effect(getter, {
lazy: true,
})
const info = {
get value () {
return effectFn()
}
}
return info
}
const diInfo=computed(()=>{
console.log('初始执行计算')
return state.s1 + state.s2
})
const handleEdit=()=>{
console.log(diInfo.value);
state.s2 = 2
console.log(diInfo.value)
}
看似没有什么问题,但是这违背了computed的缓存特性
1.只有当其依赖的东西发生变化了才需要重新计算
2.否则就返回上一次执行的结果。
为了达成这个效果我们还需要进一步优化一下
const computed=()=>{
const effectFn = effect(getter, {
lazy: true,
// 数据发生变化后,不执行注册的回调,而是执行scheduler
scheduler () {
// 数据发生了变化后,则重新设置为dirty,那么下次就会重新计算
dirty = true
}
})
let value
let dirty = true
const info = {
get value () {
// 2. 只有数据发生变化了才去重新计算
if (dirty) {
value = effectFn()
dirty = false
}
return value
}
}
return info
}
scheduler 任务调度的强大,不仅仅可以实现数组的异步批量更新、在computed和watch中也是必不可少的。
const diInfo=computed(()=>{
console.log('初始执行计算')
return state.s1 + state.s2
})
const handleEdit=()=>{
console.log(diInfo.value);
state.s2=4
console.log(diInfo.value)
}
经过以上的各种操作你是否已经对computed有了一定了解呢