먼저 간단히 살펴보겠습니다.
반응형 시스템의 핵심은 WeakMap --- Map --- Set 데이터 구조입니다.
WeakMap의 핵심은 원본 객체이고, 값은 반응형 Map입니다. 이런 방식으로 객체가 파괴되면 해당 지도도 파괴됩니다.
Map의 키는 객체의 각 속성이고, 값은 이 객체의 속성에 따라 달라지는 효과 함수의 집합입니다. 그런 다음 Proxy 개체의 get 메서드를 사용하여 개체의 속성에 의존하는 효과 함수를 키에 해당하는 Set으로 수집합니다. 또한 개체 속성을 수정할 때 개체의 set 메서드를 프록시하고 키의 모든 효과 함수를 호출해야 합니다.
지난 글에서는 이 아이디어를 바탕으로 비교적 완전한 반응형 시스템을 구현했고, 오늘도 계속해서 Computed를 구현하고 있습니다.
먼저 이전 코드를 재구성하고 종속성 컬렉션을 분리하고 종속성 함수의 실행을 추적 및 트리거 함수로 트리거합니다.
로직은 여전히 해당 Set에서 효과 함수의 실행을 트리거할 뿐만 아니라 추출하는 것이 훨씬 더 명확합니다.
그런 다음 계속해서 계산을 구현합니다.
computed의 사용법은 대략 다음과 같습니다.
const value = computed(() => { return obj.a + obj.b; });
효과 비교:
effect(() => { console.log(obj.a); });
차이점은 반환 값이 하나 더 있다는 것뿐입니다.
그래서 우리는 다음과 같이 효과를 기반으로 계산된 것을 구현합니다:
function computed(fn) { const value = effect(fn); return value }
물론 현재 효과에는 반환 값이 없으므로 추가해야 합니다.
이것은 단지 이전 실행을 기반으로 한 것입니다. 효과 함수 반환 값을 기록하고 반환합니다. 이 변환은 여전히 매우 쉽습니다.
이제 계산된 값은 계산된 값을 반환할 수 있습니다.
하지만 이제 데이터가 전달된 후 모든 효과가 실행되며 여기에서 계산된 것과 같은 효과는 매번 다시 실행할 필요가 없으며 그냥 실행됩니다. 데이터가 변경된 후.
그래서 효과가 즉시 실행되지 않고 함수를 반환하여 사용자가 직접 실행할 수 있도록 제어하는 게으른 옵션을 추가합니다.
그런 다음 계산에서 효과를 사용할 때 효과 함수가 실행되지 않고 반환되도록 지연 옵션을 추가하십시오.
computed에서 개체를 생성하고 값 가져오기가 트리거될 때 이 함수를 호출하여 최신 값을 가져옵니다.
테스트해 보겠습니다.
지금 Computed에서 반환된 값을 볼 수 있습니다. attribute는 계산된 값을 얻을 수 있으며, obj.a.를 수정한 후 계산 함수가 다시 실행되며, 값을 다시 얻으면 새로운 값을 얻을 수 있습니다.
한 번만 더 계산을 수행하면 obj.a가 변경되면 모든 효과 함수가 실행되기 때문입니다.
이렇게 하면 데이터가 변경될 때마다 계산된 함수가 다시 실행되어 계산됩니다. 최신 값.
이펙트 기능의 실행 여부를 제어할 수 있어야 하는 것은 아닙니다. 따라서 여기에 예약 기능을 추가해야 합니다.
은 schduler 콜백 기능 전달을 지원할 수 있으며, 효과를 실행할 때 스케줄러가 있으면 이를 전달하고 사용자가 다음과 같이 예약하도록 합니다. 그렇지 않으면 효과 기능이 실행됩니다.
사용자가 직접 효과 함수의 실행을 제어할 수 있도록:
그런 다음 지금 바로 코드를 실행해 보세요.
obj.a가 완료된 후에는 효과가 실행되지 않는 것을 볼 수 있습니다. 우리가 직접 예약하기 위해 sheduler를 추가했기 때문에 다시 계산하는 기능이 변경되었습니다. 이렇게 하면 데이터가 변경된 후 즉시 계산된 함수를 실행할 필요가 없으며 실행을 직접 제어할 수 있습니다.
이제 또 다른 문제가 있습니다. res.value는 액세스할 때마다 계산해야 합니다.
能不能加个缓存呢?只有数据变了才需要计算,否则直接拿之前计算的值。
当然是可以的,加个标记就行:
scheduler 被调用的时候就说明数据变了,这时候 dirty 设置为 true,然后取 value 的时候就重新计算,之后再改为 false,下次取 value 就直接拿计算好的值了。
我们测试下:
我们访问 computed 值的 value 属性时,第一次会重新计算,后面就直接拿计算好的值了。
修改它依赖的数据后,再次访问 value 属性会再次重新计算,然后后面再访问就又会直接拿计算好的值了。
至此,我们完成了 computed 的功能。
但现在的 computed 实现还有一个问题,比如这样一段代码:
let res = computed(() => { return obj.a + obj.b; }); effect(() => { console.log(res.value); });
我们在一个 effect 函数里用到了 computed 值,按理说 obj.a 变了,那 computed 的值也会变,应该触发所有的 effect 函数。
但实际上并没有:
这是为什么呢?
这是因为返回的 computed 值并不是一个响应式的对象,需要把它变为响应式的,也就是 get 的时候 track 收集依赖,set 的时候触发依赖的执行:
我们再试一下:
现在 computed 值变了就能触发依赖它的 effect 了。至此,我们的 computed 就很完善了。
完整代码如下:
const data = { a: 1, b: 2 } let activeEffect const effectStack = []; function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn); const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] return res } effectFn.deps = [] effectFn.options = options; if (!options.lazy) { effectFn() } return effectFn } function computed(fn) { let value let dirty = true const effectFn = effect(fn, { lazy: true, scheduler(fn) { if(!dirty) { dirty = true trigger(obj, 'value'); } } }); const obj = { get value() { if (dirty) { value = effectFn() dirty = false } track(obj, 'value'); console.log(obj); return value } } return obj } function cleanup(effectFn) { for (let i = 0; i < effectFn.deps.length; i++) { const deps = effectFn.deps[i] deps.delete(effectFn) } effectFn.deps.length = 0 } const reactiveMap = new WeakMap() const obj = new Proxy(data, { get(targetObj, key) { track(targetObj, key); return targetObj[key] }, set(targetObj, key, newVal) { targetObj[key] = newVal trigger(targetObj, key) } }) function track(targetObj, key) { let depsMap = reactiveMap.get(targetObj) if (!depsMap) { reactiveMap.set(targetObj, (depsMap = new Map())) } let deps = depsMap.get(key) if (!deps) { depsMap.set(key, (deps = new Set())) } deps.add(activeEffect) activeEffect.deps.push(deps); } function trigger(targetObj, key) { const depsMap = reactiveMap.get(targetObj) if (!depsMap) return const effects = depsMap.get(key) const effectsToRun = new Set(effects) effectsToRun.forEach(effectFn => { if(effectFn.options.scheduler) { effectFn.options.scheduler(effectFn) } else { effectFn() } }) }
위 내용은 Vue3 반응형 시스템에서 계산을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!