1.《Vue设计与实现》computed计算属性
let computed = (getter) => {
let effectFn = effect(getter, {
lazy: true, // 这个参数就表示,副作用函数(getter)先不执行,而是返回给effectFn变量
})
// 我们需要在返回一个对象,让这个对象在访问value属性时,才会计算对应的值
let obj = {
get value() {
return effectFn(); // 当我们在访问value属性时,才会去执行这个函数
}
}
return obj;
}
effect修改的地方,这里只是写了,lazy参数具体干什么用,没有把完整的effect实现写出来
let effect = (fn, options) => {
// 我们将副作用函数包裹一层
let effectFn = () => {
activeEffect = effectFn;
const res = fn();
return res;
}
effectFn.deps = [];
effectFn.options = options;
if (options.lazy) {
return effectFn; // 我们就先不执行这个副作用函数
} else {
effectFn(); // 否则我们就直接调用了这个函数
}
}
但是上面的计算属性,函数存在一下问题的。
那就是,当关联的响应式数据,没有发生变化时,是不应该去重新执行计算的,而是只有当关联的数据发生改变时才会重新执行计算返回。
解决方案:
- 我们用一个变量表示是否需要重新执行计算 dirty
- 因为响应式数据一旦发生了改变,就会自动触发对应的副作用函数的重新执行
- 那么我需要在这个副作用函数执行完毕之后将 dirty 转换为 true就可以了
说白了,我们就是需要去监听也好,当与计算属性相关联的响应式数据发送改变,就改变dirty变量
修改我们的computed里的effect调用时,传递scheduler表示调度执行,说白了就是将最终的执行权力交给用户自己(我自己)
修改computed
let computed = (getter) => {
let dirty = true, value;
// dirty 表示是否需要重新计算执行
// value 是表示计算结果
let effectFn = effect(getter, {
lazy: true,
scheduler(fn){
fn();
// 当我们的副作用执行完毕之后,就需要将
dirty = true; // 表示需要重新执行
}
})
let obj = {
get value() {
if (dirty) {
// 只有当dirty为true时,才会重新执行
value = effectFn();
dirty = false;
// 当执行了之后,就需要将dirty改为false,表示不需要重新执行
}
return value;
}
}
}
不好意思,这个实现还是存在一个小问题
那就是在 effect 中使用了计算属性,但是修改了响应式数据之后没有将副作用函数重新执行
什么意思呢?
let dataForm = computed(() => obj.name + obj.sex));
// 我们在effect中使用这个计算属性
effect(() => {
console.log('此时没有重新执行');
dataForm.value;
})
obj.name = 99;
当我们修改了,obj.name之后,effect参数没有重新执行。
仔细思考我们会发现,这是一个典型的effect嵌套问题
因为:
- obj这个响应式数据,的修改是会触发computed里面的effect副作用函数的执行
- 而外面的effect又没有对应的响应式数据与之关联
现在我们就需要手动触发,建立依赖关系,也需要手动触发响应
let computed = (getter) => {
let dirty = true, value;
// dirty 表示是否需要重新计算执行
// value 是表示计算结果
let effectFn = effect(getter, {
lazy: true,
scheduler(fn){
fn();
// 当我们的副作用执行完毕之后,就需要将
dirty = true; // 表示需要重新执行
trigger(obj, 'value'); // 手动触发
}
})
let obj = {
get value() {
if (dirty) {
// 只有当dirty为true时,才会重新执行
value = effectFn();
dirty = false;
// 当执行了之后,就需要将dirty改为false,表示不需要重新执行
}
track(obj, 'value'); // 其实这个value,是很随便的一个属性,意思是啥都行
return value;
}
}
}
目的就是,让当前的的副作用函数,也就是外面的effect中的父作用函数,与obj响应式数据建立联系,
这里需要解决,嵌套effect时的副作用函数覆盖的问题因为此时的effect是没有写完整的,完整之后activeEffect变量就是最外层的副作用函数了。