Heim >Web-Frontend >View.js >Was ist das Implementierungsprinzip des Vue3-Listeners?
Zuvor haben wir über berechnete Eigenschaften gesprochen, die den Wert von reaktionsfähigen Daten automatisch berechnen und zwischenspeichern können. Und wenn wir nur einige voreingestellte Vorgänge ausführen müssen, wenn sich die Antwortdaten ändern, können wir den Listener watch
verwenden. Lassen Sie uns zunächst das einfachste Beispiel implementieren und es dann Stück für Stück erweitern.
const data = {foo: 1} const obj = reactive(data) watch(obj, () => { console.log('obj已改变') })
watch
侦听器。我们还是先来实现一个最简单的例子,然后来一点一点扩充它。function watch(getter, cb) { effect( () => getter.foo ) }
在这个例子中,我们使用了watch
侦听器,当obj
的属性被改变时,控制台应该会打印出obj已改变
。基于前面我们对计算属性的实现,这里我们已经有了一个大概的思路。把watch
视为响应式对象的副作用函数,当响应式对象改变时,触发执行该副作用函数。
想要触发副作用函数,必须先收集它,还记得副作用函数是如何收集的吗?对,当响应式数据被get
时,收集副作用函数。所以首先,我们需要让watch
被响应式对象收集到。
function watch(getter, cb) { effect( () => getter.foo, { scheduler() { cb() } } ) }
接着,我们还需要让我们预设的方法被执行。当响应式数据被set
时,触发副作用函数。这里我们想触发的是cb
这个传入的回调函数,这里我们就又能用到实现计算属性时的调度器了,当调度器存在时,set
触发的trigger
会先执行调度器中的函数。
function traverse(value, seen = new Set()) { // (1) if(typeof value !== 'object' || value === null || seen.has(value)) return seen.add(value) for(const key in value) { traverse(value[key], seen) } return value }
一个简单的侦听器已经完成了!这里我们为了简单,把功能写死了,仅支持对obj.foo
的侦听。接下来,我们就要想想,如何实现对响应式对象的任意属性进行侦听?
按照前面的思路,想要实现对响应式对象的任意属性的侦听,就需要我们get
到该对象的每一个属性,这就需要我们对响应式对象进行一次递归遍历。
const data = { foo: 1 } const obj = reactive(data) watch( () => obj.foo, () => { console.log('obj.foo已改变') })
为了避免递归遍历对象时,循环引用造成的死循环,我们在(1)
处创建了Set
,当重复出现相同的对象时,直接返回。
在Vue3中,我们不能直接侦听响应式对象的属性值。如果需要侦听响应式对象的属性值,就需要一个getter
函数,让侦听器能被响应式对象收集到。
function watch(getter, cb) { if(typeof getter !== 'function') getter = traverse(getter) // (2) effect( () => getter(), { scheduler() { cb() } } ) }
指定了属性就意味着,当前的侦听器仅会被指定的属性触发,就无需递归遍历整个响应式对象了。
const data = { foo: 1 } const obj = reactive(data) watch( () => obj.foo, (newValue, oldValue) => { console.log(newValue, oldValue) })
在(2)处,我们增加了一个判断,如果传入的已经是getter
函数,我们直接使用,如果不是getter
函数,则认为是一个响应式对象,就需要进行递归遍历。
在Vue中我们还需要能够在回调函数cb()
中拿到响应式数据更新前后的新值与旧值。
function watch(getter, cb) { if(typeof getter !== 'function') getter = traverse(getter) let oldValue const effectFn = effect( () => getter(), { lazy: true, // (3) scheduler() { cb(oldValue) } } ) oldValue = effectFn() // (4) }
接下来的问题是,如何获取newValue
与oldValue
。newValue
好解决,执行完回调函数cb()
得到的就是newValue
,但这里如何获取oldValue
的值呢?要从watch
中拿到旧值,那就不能让副作用函数被立即执行。这里想到了什么?对,在实现计算属性的时候,我们用到过的lazy
,它可以禁止副作用函数自动执行。
function watch(getter, cb) { if(typeof getter !== 'function') getter = traverse(getter) let oldValue, newValue const effectFn = effect( () => getter(), { lazy: true, scheduler() { newValue = effectFn() cb(newValue, oldValue) oldValue = newValue // (5) } } ) oldValue = effectFn() }
在(3)处我们设置了lazy
开关,设置了lazy
后,副作用函数的执行权就交到了我们自己手上。在(4)处,我们手动执行了副作用函数。这里可以需要我们向前回顾一下,前面我们传入的getter
是一个函数() => obj.foo
,而effect
函数的第一个参数就是真正被执行的副作用函数,所以我们手动执行的,其实就是函数() => obj.foo
,这样我们就拿到了旧值。
如何获取新值呢?在响应式数据的值更新后,副作用函数effect
会被触发执行,当调度器属性存在时,执行调度器。在调度器中,我们可以再次执行副作用函数,通过() => obj.foo
拿到改变后的新值。
const data = { foo: 1 } const obj = reactive(data) watch( () => obj.foo, (newValue, oldValue) => { console.log('newValue:', newValue,', oldValue:', oldValue) }, { immediate: true } )
在(5)处,执行完回调函数cb()
,我们进行了一下善后工作,更新了oldValue
的值,为下一次回调做准备。
有时,我们还希望侦听器可以在创建时就立即执行回调函数。
function watch(getter, cb, options = {}) { if(typeof getter !== 'function') getter = traverse(getter) let oldValue, newValue function job() { // (6) newValue = effectFn() cb(newValue, oldValue) oldValue = newValue } const effectFn = effect( () => getter(), { lazy: true, scheduler: job, } ) if(options.immediate) { // (7) job() } else { oldValue = effectFn() } }
当immediate
的值为true
时,需要立即执行。明确了需求,我们来完善watch
In diesem Beispiel verwenden wir den watch
-Listener. Wenn die Eigenschaft von obj
geändert wird, sollte die Konsole ausdrucken, dass sich obj geändert hat Code>. Basierend auf unserer bisherigen Implementierung berechneter Eigenschaften haben wir hier bereits eine allgemeine Vorstellung. Stellen Sie sich <code>watch
als eine Nebeneffektfunktion des reaktiven Objekts vor. Wenn sich das reaktive Objekt ändert, wird die Ausführung der Nebeneffektfunktion ausgelöst.
get
sind, wird die Nebeneffektfunktion 🎜 erfasst. Zuerst müssen wir dafür sorgen, dass watch
vom reaktiven Objekt erfasst wird. 🎜rrreee🎜Als nächstes müssen wir auch unsere Standardmethode ausführen lassen. Wenn reaktive Daten festgelegt
sind, wird die Nebeneffektfunktion ausgelöst. Was wir hier auslösen möchten, ist die in cb
übergebene Rückruffunktion. Hier können wir den Scheduler verwenden, wenn der Scheduler vorhanden ist, set
Der ausgelöste trigger
führt die Funktion zunächst im Scheduler aus. 🎜rrreee🎜🎜🎜a Das Der einfache Listener ist fertig! Der Einfachheit halber haben wir die Funktion hier vollständig geschrieben und unterstützen nur das Abhören von obj.foo
. Als nächstes müssen wir darüber nachdenken, wie wir auf irgendwelche Eigenschaften des reagierenden Objekts hören können. 🎜🎜Nach der vorherigen Idee müssen wir, wenn wir auf ein Attribut des reagierenden Objekts hören möchten, jedes Attribut des Objekts abrufen
, was erfordert, dass wir eine rekursive Durchquerung des reaktiven Objekts durchführen. 🎜rrreee🎜Um die durch Zirkelverweise beim rekursiven Durchlaufen von Objekten verursachte Endlosschleife zu vermeiden, haben wir bei (1)
ein Set
erstellt, wenn dasselbe Objekt wiederholt und direkt angezeigt wird zurückkehren. 🎜🎜Abhören von Eigenschaftswerten🎜🎜In Vue3 können wir die Eigenschaftswerte reaktiver Objekte nicht direkt abhören. Wenn Sie den Eigenschaftswert des reaktiven Objekts abhören müssen, benötigen Sie eine Getter
-Funktion, damit der Listener vom reaktiven Objekt erfasst werden kann. 🎜rrreee🎜Das Angeben von Attributen bedeutet, dass der aktuelle Listener nur durch die angegebenen Attribute ausgelöst wird und nicht das gesamte reaktionsfähige Objekt rekursiv durchlaufen werden muss. 🎜rrreee🎜🎜🎜at At (2) Wir haben eine Beurteilung hinzugefügt. Wenn die übergebene Funktion bereits eine Getter
-Funktion ist, verwenden wir sie direkt. Wenn es sich nicht um eine Getter
-Funktion handelt, wird sie berücksichtigt Um ein reaktionsfähiges Objekt zu sein, ist eine rekursive Durchquerung erforderlich. 🎜🎜Hören Sie, um den neuen und alten Wert zu erhalten.🎜🎜In Vue müssen wir auch in der Lage sein, den neuen und alten Wert vor und nach der reaktionsfähigen Datenaktualisierung in der Rückruffunktion cb()
abzurufen >. 🎜rrreee🎜Die nächste Frage ist, wie man newValue
und oldValue
erhält. newValue
ist einfach zu lösen. Nach dem Ausführen der Callback-Funktion cb()
erhalten Sie newValue
, aber hier erfahren Sie, wie Sie oldValue erhalten
Was ist der Wert? Um den alten Wert von watch
zu erhalten, kann die Nebeneffektfunktion nicht sofort ausgeführt werden. Was fällt Ihnen hier ein? Ja, bei der Implementierung berechneter Eigenschaften haben wir lazy
verwendet, was die automatische Ausführung von Nebeneffektfunktionen verhindern kann. 🎜rrreee🎜Bei (3) setzen wir den Schalter lazy
. Nach dem Setzen von lazy
werden die Ausführungsrechte der Nebeneffektfunktion an uns selbst übergeben. Bei (4) führen wir die Nebeneffektfunktion manuell aus. Wir müssen hier noch einmal zurückblicken. Der Getter
, den wir zuvor übergeben haben, ist eine Funktion () => und <code>effect
Der erste Der Parameter der Funktion ist die Nebeneffektfunktion, die tatsächlich ausgeführt wird. Was wir also manuell ausführen, ist tatsächlich die Funktion () => obj.foo
, sodass wir den alten Wert erhalten. 🎜🎜Wie erhalte ich den neuen Wert? Nachdem der Wert der Antwortdaten aktualisiert wurde, wird die Ausführung der Nebeneffektfunktion effect
ausgelöst. Wenn das Scheduler-Attribut vorhanden ist, wird der Scheduler ausgeführt. Im Scheduler können wir die Nebeneffektfunktion erneut ausführen und den geänderten neuen Wert über () => obj.foo
erhalten. 🎜rrreee🎜Bei (5) haben wir nach der Ausführung der Rückruffunktion cb()
einige Nacharbeiten durchgeführt und den Wert von oldValue
aktualisiert, um uns auf den nächsten Rückruf vorzubereiten. 🎜🎜🎜🎜Manchmal haben wir Außerdem möchte ich, dass der Listener die Rückruffunktion sofort nach der Erstellung ausführt. 🎜rrreee🎜Wenn der Wert von immediate
true
ist, muss es sofort ausgeführt werden. Nachdem wir die Anforderungen geklärt haben, verbessern wir den watch
-Listener. 🎜function watch(getter, cb, options = {}) { if(typeof getter !== 'function') getter = traverse(getter) let oldValue, newValue function job() { // (6) newValue = effectFn() cb(newValue, oldValue) oldValue = newValue } const effectFn = effect( () => getter(), { lazy: true, scheduler: job, } ) if(options.immediate) { // (7) job() } else { oldValue = effectFn() } }
在(6)处,我们抽离了回调函数的执行逻辑,当options.immediate
存在时,直接触发执行。
Das obige ist der detaillierte Inhalt vonWas ist das Implementierungsprinzip des Vue3-Listeners?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!