截至前面的部分,已经是完成了响应式的基本原理。那vue还有ref和watch等等,我们就来处理一下。
1. ref函数
//ref.js
/**
* @description: ref 处理一般数据
* @param {* string | number} value 接受的值
* @return {*} 响应式数据
*/
export function ref(value) {}
先把effect里的函数跟ref关联起来哈。
import { reactive } from "./reactive.js";
import { effect } from "./effect.js";
import { ref } from "./ref.js";
const state = ref(1);
effect(() => {
console.log("effect", state.value);
});
现在来写ref,首先是返回一个对象,对象有个属性是value。当访问这个属性的时候,进行依赖收集;当修改属性值的时候,进行派发更新。
//ref.js
export function ref(value) {
return {
get value() {
track(this, TrackOpTypes.GET, "value");
return value;
},
set(newValue) {
value = newValue;
trigger(this, TriggerOpTypes.SET, "value");
},
};
}
2.computed函数
//index.js
const state = reactive({
a: 1,
b: 2,
});
const sum = computed(() => {
console.log("computed");
return state.a + state.b;
});
sum.value;
//computed.js
//参数可能是函数也可能是对象,对参数进行归一化
function normalizeParameter(getterOrOptions) {
let getter, setter;
if (typeof getterOrOptions === "function") {
getter = getterOrOptions;
setter = () => {
console.warn(`Computed property was assigned to but it has no setter`);
};
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return { getter, setter };
}
export function computed(getterOrOptions) {
const { setter, getter } = normalizeParameter(getterOrOptions);
effect(getter);//把函数交个 effect
}
现在是立即执行的,但vue里的计算属性是当你去访问它的值的时候才执行,那我们就设置下,当访问value的时候才执行。
export function computed(getterOrOptions) {
const { setter, getter } = normalizeParameter(getterOrOptions);
const effceFn = effect(getter, {
lazy: true,
});
const obj = {
get value() {
return effceFn();
},
};
return obj;
}
这样,当我们访问.value的时候,就会触发get,进而执行effect,进行关联。
但现在如果多次读这个属性,会多次触发。但vue的计算属性是有缓存的。如果发现结果没有变化,就会把结果给你,然后不再运行。
vue有个变量叫做dirty,用来决定要不要进行依赖收集和派发更新。
export function computed(getterOrOptions) {
const { setter, getter } = normalizeParameter(getterOrOptions);
let value,
dirty = true;
const effceFn = effect(getter, {
lazy: true,
});
const obj = {
get value() {
//第一次 赋值
if (dirty) {
value = effceFn();
dirty = false;
}
return value;
},
};
return obj;
}
现在只执行一次了,但是还有个问题。
const sum = computed(() => {
console.log("computed");
return state.a + state.b;
});
console.log("sum.value", sum.value);
console.log("sum.value", sum.value);
console.log("sum.value", sum.value);
state.a++
state.a++
state.a++
state.a++
console.log("sum.value", sum.value);
把
state.a变化了,依赖发生变化,但是值却还是之前的值,没发生变化。因为dirty变量变为false,那就不再依赖收集了。依赖发生变化后,dirty应该变为true。
const effceFn = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true; // 变为true 进入get 进行依赖收集
effceFn();
},
});
之前的打印还发现一个问题,没有使用computed返回的值,但重新运行了。所以依赖发生变化了,只需要进行标记,说明数据脏了,不需要重新运行。
const effceFn = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true;
// effceFn(); 不用运行
},
});
还有个问题,当我们不是在js,或者说是在模板里使用,这种情况呢?
function render() {
console.log("render", sum.value);
}
effect(render);
vue的render函数就是放在effect里运行的,这里就算是模拟在模板中使用了。
刚才是会执行这个render函数,拿到值。但当我们去修改state.a的值的时候,并没有重新运行,这不扯淡嘛,我修改了值,页面不更新😑😑😑
分析一下,a变化了,要派发更新,但是走着走进那个函数之后,发现有scheduler,那就执行scheduler,而scheduler并没有将数据与函数进行关联,所以手动地加上派发更新就好了。
const effceFn = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true;
trigger(obj, TriggerOpTypes.SET, "value");
},
});
get value() {
track(obj, TrackOpTypes.GET, "value");
if (dirty) {
value = effceFn();
dirty = false;
}
return value;
},
最后再把setter加上。
const obj = {
get value() {
track(obj, TrackOpTypes.GET, "value");
if (dirty) {
value = effceFn();
dirty = false;
}
return value;
},
set value(newValue) {
setter(newValue);
},
};
到这里,ref和computed就完成了,至于其他的,懂这些基础应该就等你慢慢摸索出来了。