Heim  >  Artikel  >  Web-Frontend  >  Was ist das Implementierungsprinzip des Vue3-Listeners?

Was ist das Implementierungsprinzip des Vue3-Listeners?

王林
王林nach vorne
2023-05-16 15:04:061386Durchsuche

Auf reaktionsfähige Objekte hören

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
}

Was ist das Implementierungsprinzip des Vue3-Listeners?

一个简单的侦听器已经完成了!这里我们为了简单,把功能写死了,仅支持对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)
})

Was ist das Implementierungsprinzip des Vue3-Listeners?

在(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)
}

接下来的问题是,如何获取newValueoldValuenewValue好解决,执行完回调函数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的值,为下一次回调做准备。

Was ist das Implementierungsprinzip des Vue3-Listeners?

有时,我们还希望侦听器可以在创建时就立即执行回调函数。

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时,需要立即执行。明确了需求,我们来完善watchIn 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.

🎜Wenn Sie eine Nebenwirkungsfunktion auslösen möchten, müssen Sie diese zuerst erfassen. Erinnern Sie sich, wie die Nebenwirkungsfunktion erfasst wird? Ja, 🎜Wenn reaktive Daten 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, setDer ausgelöste trigger führt die Funktion zunächst im Scheduler aus. 🎜rrreee🎜Was ist das Implementierungsprinzip des Vue3-Listeners🎜🎜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🎜Was ist das Implementierungsprinzip des Vue3-Listeners🎜🎜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. 🎜🎜Was ist das Implementierungsprinzip des Vue3-Listeners🎜🎜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 !== &#39;function&#39;) 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存在时,直接触发执行。

Was ist das Implementierungsprinzip des Vue3-Listeners?

实现效果

Was ist das Implementierungsprinzip des Vue3-Listeners?

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!

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