Heim  >  Artikel  >  Web-Frontend  >  So schreiben Sie das Vue3-Responsive-System handschriftlich

So schreiben Sie das Vue3-Responsive-System handschriftlich

WBOY
WBOYnach vorne
2023-05-14 09:40:05945Durchsuche

Responsive

Was ist zunächst einmal responsiv?

Reaktionsfähigkeit besteht darin, eine Reihe von Verknüpfungsverarbeitungen durchzuführen, wenn sich die beobachteten Daten ändern. Genau wie bei einem heißen gesellschaftlichen Ereignis werden Medien von allen Seiten bei einem Update nachverfolgen und relevante Berichte erstellen. Dabei sind gesellschaftliche Topereignisse die zu beobachtenden Ziele. Was ist also im Front-End-Framework das beobachtete Ziel? Offensichtlich ist es der Staat. Im Allgemeinen gibt es mehrere Zustände, die nach Objekten organisiert sind. Daher können wir die Änderungen jedes Schlüssels des Zustandsobjekts beobachten und eine Reihe von Prozessen in Verbindung ausführen.

Wir müssen eine solche Datenstruktur beibehalten:

So schreiben Sie das Vue3-Responsive-System handschriftlich

Jeder Schlüssel des Zustandsobjekts verfügt über eine zugehörige Reihe von Effekt-Nebenwirkungsfunktionen, dh die Logik der verknüpften Ausführung, wenn sie sich ändert, organisiert nach Set .

Jede Taste ist mit einer Reihe von Effektfunktionen verknüpft, und in einer Karte können mehrere Tasten verwaltet werden.

Diese Karte existiert, wenn das Objekt existiert, wenn das Objekt zerstört wird. (Da die Objekte weg sind, müssen die mit jedem Schlüssel verbundenen Effekte nicht beibehalten werden.)

Und WeakMap verfügt über eine solche Funktion. Der Schlüssel von WeakMap muss ein Objekt sein, und der Wert kann ein beliebiger Datenwert sein Wenn das Schlüsselobjekt zerstört wird, wird auch der Wert zerstört.

Die responsive Karte wird also mit WeakMap gespeichert und der Schlüssel ist das Originalobjekt.

Diese Datenstruktur ist die Kerndatenstruktur der Reaktionsfähigkeit.

Zum Beispiel dieses Statusobjekt:

const obj = {
    a: 1,
    b: 2
}

Seine responsive Datenstruktur sieht so aus:

const depsMap = new Map();
const aDeps = new Set();
depsMap.set('a', aDeps);
const bDeps = new Set();
depsMap.set('b', bDeps);
const reactiveMap = new WeakMap()
reactiveMap.set(obj, depsMap);

Die erstellte Datenstruktur ist die im Bild:

So schreiben Sie das Vue3-Responsive-System handschriftlich

So schreiben Sie das Vue3-Responsive-System handschriftlich

Dann hinzufügen Deps-Abhängigkeiten. Wenn beispielsweise eine Funktion von a abhängt, muss sie zur Deps-Sammlung von a hinzugefügt werden:

effect(() => {
    console.log(obj.a);
});

Das heißt, es ist so:

const depsMap = reactiveMap.get(obj);
const aDeps = depsMap.get('a');
aDeps.add(该函数);

Es gibt kein Problem bei der Aufrechterhaltung der Deps-Funktion in auf diese Weise, aber müssen wir Benutzern erlauben, es manuell hinzuzufügen? Dies greift nicht nur in den Geschäftscode ein, sondern ist auch leicht zu übersehen.

Deshalb werden wir Benutzern definitiv nicht erlauben, Deps manuell zu verwalten, sondern eine automatische Abhängigkeitserfassung durchführen. Wie kann man Abhängigkeiten automatisch erfassen? Beim Lesen des Statuswerts wird eine Abhängigkeitsbeziehung zum Status hergestellt, sodass man sich leicht die Get-Methode vorstellen kann, die als Proxy für den Status verwendet werden kann. Entweder über Object.defineProperty oder Proxy:

const data = {
    a: 1,
    b: 2
}
let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}
const reactiveMap = new WeakMap()
const obj = new Proxy(data, {
    get(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)

        return targetObj[key]
   }
})

effect führt die übergebene Rückruffunktion fn aus. Wenn Sie obj.a in fn lesen, wird get ausgelöst und die entsprechende Deps-Sammlung abgerufen zu a und fügen Sie die aktuelle Effektfunktion hinzu.

Damit ist eine Abhängigkeitssammlung abgeschlossen.

Wenn Sie obj.a ändern, müssen Sie alle Deps benachrichtigen, daher müssen Sie auch einen Proxy festlegen:

set(targetObj, key, newVal) {
    targetObj[key] = newVal
    const depsMap = reactiveMap.get(targetObj)
    if (!depsMap) return
    const effects = depsMap.get(key)
    effects && effects.forEach(fn => fn())
}

Die grundlegende Reaktionsfähigkeit ist abgeschlossen. Testen wir sie:

So schreiben Sie das Vue3-Responsive-System handschriftlich

Zweimal gedruckt Das erste Mal ist 1, das zweite Mal ist 3. Der Effekt führt zuerst die eingehende Rückruffunktion aus und löst get aus, um die Abhängigkeiten zu sammeln. Zu diesem Zeitpunkt ist das gedruckte obj.a 1. Wenn obj.a dann der Wert 3 zugewiesen wird, wird set ausgelöst, um die gesammelten Abhängigkeiten auszuführen . Zu diesem Zeitpunkt ist obj. a 3

Die Abhängigkeiten wurden ebenfalls korrekt erfasst:

So schreiben Sie das Vue3-Responsive-System handschriftlich

Das Ergebnis ist korrekt, wir haben die grundlegende Reaktionsfähigkeit abgeschlossen! Natürlich ist die Reaktionsfähigkeit nicht nur auf diesen kleinen Code zurückzuführen. Unsere aktuelle Implementierung ist nicht perfekt und es gibt immer noch einige Probleme. Beispiel: Wenn der Code einen Verzweigungsschalter enthält, hängt die letzte Ausführung von obj.b ab, die nächste Ausführung jedoch nicht davon. Gibt es zu diesem Zeitpunkt eine ungültige Abhängigkeit?

Dieser Code:

const obj = {
    a: 1,
    b: 2
}
effect(() => {
    console.log(obj.a ? obj.b : 'nothing');
});
obj.a = undefined;
obj.b = 3;

Bei der ersten Ausführung der Effektfunktion ist obj.a 1. Zu diesem Zeitpunkt geht es zum ersten Zweig und verlässt sich auf obj.b. Ändern Sie obj.a in „undefiniert“, setzen Sie den Trigger und führen Sie alle abhängigen Funktionen aus. Zu diesem Zeitpunkt gehen wir zu Zweig zwei und sind nicht mehr von obj.b abhängig.

Ändern Sie obj.b in 3. Es liegt auf der Hand, dass es derzeit keine Funktion gibt, die von b abhängt. Probieren wir es aus:

So schreiben Sie das Vue3-Responsive-System handschriftlich

Der erste Ausdruck von 2 ist richtig, das heißt wir Wir haben den ersten Zweig erreicht und drucken obj.b

Es ist auch richtig, zum zweiten Mal nichts zu drucken. Zu diesem Zeitpunkt gehen wir zum zweiten Zweig. Aber zum dritten Mal nichts zu drucken ist falsch, da obj.b zu diesem Zeitpunkt keine abhängigen Funktionen mehr hat, aber trotzdem gedruckt wird.

Drucken Sie die Deps aus und schauen Sie sich sie an. Sie werden feststellen, dass die Deps von obj.b nicht gelöscht sind

So schreiben Sie das Vue3-Responsive-System handschriftlich

所以解决方案就是每次添加依赖前清空下上次的 deps。怎么清空某个函数关联的所有 deps 呢?记录下就好了。

我们改造下现有的 effect 函数:

let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}

记录下这个 effect 函数被放到了哪些 deps 集合里。也就是:

let activeEffect
function effect(fn) {
  const effectFn = () => {
      activeEffect = effectFn
      fn()
  }
  effectFn.deps = []
  effectFn()
}

对之前的 fn 包一层,在函数上添加个 deps 数组来记录被添加到哪些依赖集合里。

get 收集依赖的时候,也记录一份到这里:

So schreiben Sie das Vue3-Responsive-System handschriftlich

这样下次再执行这个 effect 函数的时候,就可以把这个 effect 函数从上次添加到的依赖集合里删掉:

So schreiben Sie das Vue3-Responsive-System handschriftlich

cleanup 实现如下:

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}

effectFn.deps 数组记录了被添加到的 deps 集合,从中删掉自己。全删完之后就把上次记录的 deps 数组置空。

我们再来测试下:

So schreiben Sie das Vue3-Responsive-System handschriftlich

无限循环打印了,什么鬼?

问题出现在这里:

So schreiben Sie das Vue3-Responsive-System handschriftlich

set 的时候会执行所有的当前 key 的 deps 集合里的 effect 函数。

而我们执行 effect 函数之前会把它从之前的 deps 集合中清掉:

So schreiben Sie das Vue3-Responsive-System handschriftlich

执行的时候又被添加到了 deps 集合。这样 delete 又 add,delete 又 add,所以就无限循环了。

解决的方式就是创建第二个 Set,只用于遍历:

So schreiben Sie das Vue3-Responsive-System handschriftlich

这样就不会无限循环了。

再测试一次:

So schreiben Sie das Vue3-Responsive-System handschriftlich

现在当 obj.a 赋值为 undefined 之后,再次执行 effect 函数,obj.b 的 deps 集合就被清空了,所以需改 obj.b 也不会打印啥。

看下现在的响应式数据结构:

So schreiben Sie das Vue3-Responsive-System handschriftlich

确实,b 的 deps 集合被清空了。那现在的响应式实现是完善的了么?也不是,还有一个问题:

如果 effect 嵌套了,那依赖还能正确的收集么?

首先讲下为什么要支持 effect 嵌套,因为组件是可以嵌套的,而且组件里会写 effect,那也就是 effect 嵌套了,所以必须支持嵌套。

我们嵌套下试试:

effect(() => {
    console.log(&#39;effect1&#39;);
    effect(() => {
        console.log(&#39;effect2&#39;);
        obj.b;
    });
    obj.a;
});
obj.a = 3;

按理说会打印一次 effect1、一次 effect2,这是最开始的那次执行。然后 obj.a 修改为 3 后,会触发一次 effect1 的打印,执行内层 effect,又触发一次 effect2 的打印。也就是会打印 effect1、effect2、effect1、effect2。

我们测试下:

So schreiben Sie das Vue3-Responsive-System handschriftlich

打印了 effect1、effet2 这是对的,但第三次打印的是 effect2,这说明 obj.a 修改后并没有执行外层函数,而是执行的内层函数。为什么呢?

看下这段代码:

So schreiben Sie das Vue3-Responsive-System handschriftlich

我们执行 effect 的时候,会把它赋值给一个全局变量 activeEffect,然后后面收集依赖就用的这个。

当嵌套 effect 的时候,内层函数执行后会修改 activeEffect 这样收集到的依赖就不对了。

怎么办呢?嵌套的话加一个栈来记录 effect 不就行了?

也就是这样:

So schreiben Sie das Vue3-Responsive-System handschriftlich

执行 effect 函数前把当前 effectFn 入栈,执行完以后出栈,修改 activeEffect 为栈顶的 effectFn。

这样就保证了收集到的依赖是正确的。

这种思想的应用还是很多的,需要保存和恢复上下文的时候,都是这样加一个栈。

我们再测试一下:

So schreiben Sie das Vue3-Responsive-System handschriftlich

现在的打印就对了。至此,我们的响应式系统就算比较完善了。

全部代码如下:

const data = {
    a: 1,
    b: 2
}
let activeEffect
const effectStack = [];
function effect(fn) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      fn()
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
  }
  effectFn.deps = []
  effectFn()
}
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) {
        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);
        return targetObj[key]
   },
   set(targetObj, key, newVal) {
        targetObj[key] = newVal
        const depsMap = reactiveMap.get(targetObj)
        if (!depsMap) return
        const effects = depsMap.get(key)
        // effects && effects.forEach(fn => fn())
        const effectsToRun = new Set(effects);
        effectsToRun.forEach(effectFn => effectFn());
    }
})

总结

响应式就是数据变化的时候做一系列联动的处理。

核心是这样一个数据结构:

So schreiben Sie das Vue3-Responsive-System handschriftlich

最外层是 WeakMap,key 为对象,value 为响应式的 Map。这样当对象销毁时,Map 也会销毁。Map 里保存了每个 key 的依赖集合,用 Set 组织。

我们通过 Proxy 来完成自动的依赖收集,也就是添加 effect 到对应 key 的 deps 的集合里。 set 的时候触发所有的 effect 函数执行。

这就是基本的响应式系统。

但是还不够完善,每次执行 effect 前要从上次添加到的 deps 集合中删掉它,然后重新收集依赖。这样可以避免因为分支切换产生的无效依赖。并且执行 deps 中的 effect 前要创建一个新的 Set 来执行,避免 add、delete 循环起来。此外,为了支持嵌套 effect,需要在执行 effect 之前把它推到栈里,然后执行完出栈。解决了这几个问题之后,就是一个完善的 Vue 响应式系统了。当然,现在虽然功能是完善的,但是没有实现 computed、watch 等功能,之后再实现。

最后,再来看一下这个数据结构,理解了它就理解了 vue 响应式的核心:

So schreiben Sie das Vue3-Responsive-System handschriftlich

Das obige ist der detaillierte Inhalt vonSo schreiben Sie das Vue3-Responsive-System handschriftlich. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen