Tout d'abord, passons brièvement en revue :
Le cœur du système réactif est une WeakMap --- Map --- Set structure de données.
La clé de WeakMap est l'objet d'origine et la valeur est la carte réactive. De cette façon, lorsque l’objet est détruit, la Carte correspondante sera également détruite. La clé de
Map est chaque attribut de l'objet, et la valeur est un ensemble de fonctions d'effet qui dépendent des attributs de cet objet. Utilisez ensuite la méthode get de l'objet proxy Proxy pour collecter les fonctions d'effet qui dépendent des propriétés de l'objet dans le Set correspondant à la clé. Il est également nécessaire de proxy la méthode set de l'objet et d'appeler toutes les fonctions d'effet de la clé lors de la modification des propriétés de l'objet.
Dans le dernier article, nous avons implémenté un système réactif relativement complet basé sur cette idée, et aujourd'hui nous continuons à implémenter le calcul.
Tout d'abord, nous reconstruisons le code précédent et séparons la collection de dépendances et déclenchons l'exécution des fonctions de dépendance en fonctions de suivi et de déclenchement :
La logique est toujours d'ajouter des effets au Set correspondant, En plus de déclencher l'exécution de la fonction d'effet dans le Set correspondant, mais il est beaucoup plus clair de l'extraire.
Ensuite, continuez à mettre en œuvre le calcul. L'utilisation de
calculed ressemble à ceci :
const value = computed(() => { return obj.a + obj.b; });
Comparez l'effet :
effect(() => { console.log(obj.a); });
La différence n'est qu'une valeur de retour supplémentaire.
Nous implémentons donc un calcul basé sur un effet comme celui-ci :
function computed(fn) { const value = effect(fn); return value }
Bien sûr, l'effet actuel n'a pas de valeur de retour, nous devons l'ajouter :
C'est juste basé sur l'exécution précédente du fonction d'effet Enregistrez la valeur de retour et renvoyez-la. Cette transformation est toujours très simple.
Maintenant, calculé peut renvoyer la valeur calculée :
Mais maintenant les données sont transmises, tous les effets sont exécutés, et les effets comme calculés ici n'ont pas besoin d'être réexécutés à chaque fois, juste exécutés après les données ont changé.
Nous ajoutons donc une option paresseuse pour contrôler que l'effet ne soit pas exécuté immédiatement, mais pour renvoyer la fonction et laisser l'utilisateur l'exécuter lui-même.
Ensuite, lorsque vous utilisez l'effet dans le calcul, ajoutez une option paresseuse pour que la fonction d'effet ne s'exécute pas mais retourne.
Créez un objet calculé, appelez cette fonction lorsque la valeur get est déclenchée pour obtenir la dernière valeur :
Testons-la :
Vous pouvez voir la valeur renvoyée par calculé maintenant. L'attribut peut obtenir la valeur calculée, et après avoir modifié obj.a., la fonction de calcul sera réexécutée et la nouvelle valeur pourra être obtenue lorsque la valeur sera à nouveau obtenue.
Effectuez simplement un calcul supplémentaire. En effet, toutes les fonctions d'effet seront exécutées lorsque obj.a change :
De cette façon, chaque fois que les données changent, la fonction calculée sera réexécutée pour calculer. la dernière valeur.
Ce n'est pas nécessaire. Que la fonction d'effet soit exécutée ou non doit être contrôlable. Nous devons donc y ajouter la fonction de planification :
peut prendre en charge la transmission de la fonction de rappel schduler, puis lors de l'exécution de l'effet, s'il existe un planificateur, transmettez-le-lui et laissez l'utilisateur le planifier. lui-même, sinon la fonction d'effet sera exécutée.
Pour que les utilisateurs puissent contrôler eux-mêmes l'exécution de la fonction d'effet :
Ensuite, essayez le code tout de suite :
Vous pouvez voir que l'effet n'est pas exécuté après que obj.a soit a changé la fonction pour recalculer, car nous avons ajouté un planificateur pour le planifier nous-mêmes. Cela évite d'avoir à exécuter la fonction calculée immédiatement après les modifications des données et vous pouvez contrôler l'exécution vous-même.
Maintenant, il y a un autre problème, res.value doit être calculé à chaque accès :
能不能加个缓存呢?只有数据变了才需要计算,否则直接拿之前计算的值。
当然是可以的,加个标记就行:
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() } }) }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!