まず最初に、簡単に復習しましょう:
レスポンシブ システムの中核は、WeakMap --- Map --- Set データ構造です。
#WeakMap のキーは元のオブジェクトで、値は応答する Map です。このように、オブジェクトが破棄されると、対応するマップも破棄されます。
Map のキーはオブジェクトの各属性であり、値はこのオブジェクトの属性に依存する一連の効果関数です。次に、Proxy プロキシ オブジェクトの get メソッドを使用して、オブジェクトのプロパティに依存するエフェクト関数をキーに対応する Set に収集します。オブジェクトのプロパティを変更するときに、オブジェクトの set メソッドをプロキシし、キーのすべてのエフェクト関数を呼び出すことも必要です。
前回の記事では、このアイデアに基づいて比較的完全なレスポンシブ システムを実装しました。その後、今日もコンピューティングの実装を続けます。
まず、前のコードを再構築し、依存関係の収集と依存関係関数のトリガーの実行をトラック関数とトリガー関数に分離します。
ロジックは依然として対応するセットにエフェクトを追加し、対応するセット内でエフェクト関数の実行をトリガーするものですが、抽象化するとより明確になります。
次に、computed の実装を続けます。
computed の使用法はおおよそ次のようになります:
const value = computed(() => { return obj.a + obj.b; });
効果を比較します:
effect(() => { console.log(obj.a); });
違いは 1 つだけです戻り値が増えます。
したがって、次のような効果に基づいて計算を実装します:
function computed(fn) { const value = effect(fn); return value }
もちろん、現在の効果には戻り値がないため、それを追加する必要があります。
戻り値を記録し、エフェクト関数の以前の実行に基づいてそれを返すだけです。この変換も非常に簡単です。
Now computed は計算値を返すことができます:
ただし、データが渡され、すべてのエフェクトが実行されます。エフェクトはここにあります。 like computed は毎回再実行する必要はなく、データが変更された後にのみ実行する必要があります。
そこで、エフェクトがすぐに実行されないように制御するための遅延オプションを追加しますが、関数を返してユーザーが自分で実行できるようにします。
次に、計算結果でエフェクトを使用する場合、エフェクト関数が実行されずに返されるように、遅延オプションを追加します。
計算結果でオブジェクトを作成し、値の取得がトリガーされたときにこの関数を呼び出して最新の値を取得します:
テストしてみましょう:
計算された戻り値の value 属性が計算された値を取得できるようになり、obj.a が変更されたことがわかります。その後、計算関数が再実行され、再度値を取得すると新しい値が得られます。
もう 1 つの計算を実行します。これは、obj.a が変更されるとすべてのエフェクト関数が実行されるためです。
この例では、つまり、データが変更されるたびに、計算関数が再実行されて最新の値が計算されます。
これは必須ではありませんが、エフェクト機能を実行するかどうかも制御できるようにする必要があります。したがって、それにスケジューリング関数を追加する必要があります。
は、スケジューラー コールバック関数の受け渡しをサポートし、スケジューラーがある場合はエフェクトの実行時に実行できます。 、それを渡します。ユーザーが自分でスケジュールできるようにします。そうでない場合は、エフェクト関数が実行されます。
この方法で、ユーザーはエフェクト関数の実行を制御できます:
その後、コードをもう一度試してください:
#ご覧のとおり、obj.a が変更された後、エフェクト関数は再計算のために実行されませんでした。これは、独自にスケジュールするためにシェデュラーを追加したためです。これにより、データ変更直後に計算された関数を実行する必要がなくなり、実行を自分で制御できるようになります。
ここで別の問題が発生します。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 中国語 Web サイトの他の関連記事を参照してください。