Maison >interface Web >Voir.js >Quel est le principe de mise en œuvre de l'écouteur Vue3

Quel est le principe de mise en œuvre de l'écouteur Vue3

王林
王林avant
2023-05-16 15:04:061435parcourir

Écoute d'objets réactifs

Plus tôt, nous avons parlé des propriétés calculées, qui peuvent automatiquement calculer et mettre en cachela valeur des données réactives. Et si nous devons uniquement effectuer certaines opérations prédéfinies lorsque les données réactives changent, nous pouvons utiliser l'écouteur watch. Implémentons d’abord l’exemple le plus simple, puis développons-le petit à petit. watch侦听器。我们还是先来实现一个最简单的例子,然后来一点一点扩充它。

const data = {foo: 1}
const obj = reactive(data)
watch(obj, () => {
  console.log('obj已改变')
})

在这个例子中,我们使用了watch侦听器,当obj的属性被改变时,控制台应该会打印出obj已改变。基于前面我们对计算属性的实现,这里我们已经有了一个大概的思路。把watch视为响应式对象的副作用函数,当响应式对象改变时,触发执行该副作用函数。

想要触发副作用函数,必须先收集它,还记得副作用函数是如何收集的吗?对,当响应式数据被get时,收集副作用函数。所以首先,我们需要让watch被响应式对象收集到。     

function watch(getter, cb) {
  effect(
    () => getter.foo
  )
}

接着,我们还需要让我们预设的方法被执行。当响应式数据被set时,触发副作用函数。这里我们想触发的是cb这个传入的回调函数,这里我们就又能用到实现计算属性时的调度器了,当调度器存在时,set触发的trigger会先执行调度器中的函数。

function watch(getter, cb) {
  effect(
    () => getter.foo,
    {
      scheduler() {
        cb()
      }
    }
  )
}

Quel est le principe de mise en œuvre de lécouteur Vue3

一个简单的侦听器已经完成了!这里我们为了简单,把功能写死了,仅支持对obj.foo的侦听。接下来,我们就要想想,如何实现对响应式对象的任意属性进行侦听?

按照前面的思路,想要实现对响应式对象的任意属性的侦听,就需要我们get到该对象的每一个属性,这就需要我们对响应式对象进行一次递归遍历。

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
}

为了避免递归遍历对象时,循环引用造成的死循环,我们在(1)处创建了Set,当重复出现相同的对象时,直接返回。

侦听属性值

在Vue3中,我们不能直接侦听响应式对象的属性值。如果需要侦听响应式对象的属性值,就需要一个getter函数,让侦听器能被响应式对象收集到。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  () => {
  console.log('obj.foo已改变')
})

指定了属性就意味着,当前的侦听器仅会被指定的属性触发,就无需递归遍历整个响应式对象了。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter) // (2)
  effect(
    () => getter(),
    {
      scheduler() {
        cb()
      }
    }
  )
}

Quel est le principe de mise en œuvre de lécouteur Vue3

在(2)处,我们增加了一个判断,如果传入的已经是getter函数,我们直接使用,如果不是getter函数,则认为是一个响应式对象,就需要进行递归遍历。

侦听获取新值和旧值

在Vue中我们还需要能够在回调函数cb()中拿到响应式数据更新前后的新值与旧值。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

接下来的问题是,如何获取newValueoldValuenewValue好解决,执行完回调函数cb()得到的就是newValue,但这里如何获取oldValue的值呢?要从watch中拿到旧值,那就不能让副作用函数被立即执行。这里想到了什么?对,在实现计算属性的时候,我们用到过的lazy,它可以禁止副作用函数自动执行。

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

在(3)处我们设置了lazy开关,设置了lazy后,副作用函数的执行权就交到了我们自己手上。在(4)处,我们手动执行了副作用函数。这里可以需要我们向前回顾一下,前面我们传入的getter是一个函数() => obj.foo,而effect函数的第一个参数就是真正被执行的副作用函数,所以我们手动执行的,其实就是函数() => obj.foo,这样我们就拿到了旧值。

如何获取新值呢?在响应式数据的值更新后,副作用函数effect会被触发执行,当调度器属性存在时,执行调度器。在调度器中,我们可以再次执行副作用函数,通过() => obj.foo拿到改变后的新值。

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

在(5)处,执行完回调函数cb(),我们进行了一下善后工作,更新了oldValue的值,为下一次回调做准备。

Quel est le principe de mise en œuvre de lécouteur Vue3

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

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
      console.log('newValue:', newValue,', oldValue:', oldValue)
  },
  { immediate: true }
)

immediate的值为true时,需要立即执行。明确了需求,我们来完善watch

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

Dans cet exemple, nous utilisons l'écouteur watch Lorsque la propriété de obj est modifiée, la console doit afficher que obj a changé code>. Sur la base de notre précédente implémentation des propriétés calculées, nous avons déjà ici une idée générale. Considérez <code>watch comme une fonction d'effet secondaire de l'objet réactif. Lorsque l'objet réactif change, l'exécution de la fonction d'effet secondaire est déclenchée. 🎜🎜Si vous souhaitez déclencher une fonction d'effet secondaire, vous devez d'abord la collecter. Vous souvenez-vous de la façon dont la fonction d'effet secondaire est collectée ? Oui, 🎜Lorsque les données réactives sont get, la fonction d'effet secondaire 🎜 est collectée. Donc d'abord, nous devons faire en sorte que watch soit collecté par l'objet réactif.​ 🎜rrreee🎜Ensuite, nous devons également laisser notre méthode par défaut être exécutée. Lorsque les données réactives sont définies, la fonction d'effet secondaire est déclenchée. Ce que nous voulons déclencher ici, c'est la fonction de rappel passée dans cb. Ici, nous pouvons utiliser le planificateur lors de l'implémentation des propriétés calculées. Lorsque le planificateur existe, setLe déclenché. trigger exécutera d'abord la fonction dans le planificateur. 🎜rrreee🎜Quel est le principe de mise en œuvre de l'écouteur Vue3🎜🎜a Le un simple auditeur est complet ! Ici, par souci de simplicité, nous avons écrit la fonction à mort et ne prend en charge que l'écoute de obj.foo. Ensuite, nous devons réfléchir à la manière d'écouter les propriétés de l'objet réactif ? 🎜🎜Selon l'idée précédente, si nous voulons écouter n'importe quel attribut de l'objet réactif, nous devons obtenir chaque attribut de l'objet, ce qui nous oblige à effectuer un parcours récursif d'objet réactif. 🎜rrreee🎜Afin d'éviter la boucle infinie provoquée par les références circulaires lors du parcours récursif d'objets, nous avons créé un Set en (1) lorsque le même objet apparaît à plusieurs reprises, directement. retour. 🎜🎜Écoute des valeurs de propriété🎜🎜Dans Vue3, nous ne pouvons pas écouter directement les valeurs de propriété des objets réactifs. Si vous devez écouter la valeur de la propriété de l'objet réactif, vous avez besoin d'une fonction getter pour que l'écouteur puisse être collecté par l'objet réactif. 🎜rrreee🎜Spécifier des attributs signifie que l'écouteur actuel ne sera déclenché que par les attributs spécifiés, et il n'est pas nécessaire de parcourir de manière récursive l'intégralité de l'objet réactif. 🎜rrreee🎜Quel est le principe de mise en œuvre de l'écouteur Vue3🎜🎜à (2), nous avons ajouté un jugement. Si la fonction transmise est déjà une fonction getter, nous l'utilisons directement. Si ce n'est pas une fonction getter, elle est prise en compte. pour être un objet réactif, un parcours récursif est nécessaire. 🎜🎜Écoutez pour obtenir la nouvelle valeur et l'ancienne valeur🎜🎜Dans Vue, nous devons également pouvoir obtenir la nouvelle valeur et l'ancienne valeur avant et après la mise à jour des données réactives dans la fonction de rappel cb(). 🎜rrreee🎜La question suivante est de savoir comment obtenir newValue et oldValue. newValue est facile à résoudre. Après avoir exécuté la fonction de rappel cb(), vous obtenez newValue, mais voici comment obtenir oldValue. Quelle est la valeur ? Pour obtenir l'ancienne valeur de watch, la fonction d'effet secondaire ne peut pas être exécutée immédiatement. Qu’est-ce qui me vient à l’esprit ici ? Oui, lors de l'implémentation des propriétés calculées, nous avons utilisé lazy, qui peut interdire l'exécution automatique des fonctions d'effets secondaires. 🎜rrreee🎜En (3), nous définissons le commutateur lazy. Après avoir défini lazy, les droits d'exécution de la fonction d'effet secondaire nous sont confiés. En (4), nous exécutons manuellement la fonction d’effet secondaire. Nous devons revenir en arrière ici. Le getter que nous avons transmis plus tôt est une fonction () => Le paramètre de la fonction est la fonction d'effet secondaire qui est réellement exécutée, donc ce que nous exécutons manuellement est en fait la fonction () => obj.foo, nous obtenons donc l'ancienne valeur. 🎜🎜Comment obtenir la nouvelle valeur ? Une fois la valeur des données réactives mise à jour, l'exécution de la fonction d'effet secondaire effect sera déclenchée. Lorsque l'attribut du planificateur existe, le planificateur sera exécuté. Dans le planificateur, nous pouvons exécuter à nouveau la fonction d'effet secondaire et obtenir la nouvelle valeur modifiée via () => obj.foo. 🎜rrreee🎜À (5), après avoir exécuté la fonction de rappel cb(), nous avons effectué quelques travaux ultérieurs et mis à jour la valeur de oldValue pour préparer le prochain rappel. 🎜🎜Quel est le principe de mise en œuvre de l'écouteur Vue3🎜🎜Parfois, nous souhaitez également que l'écouteur exécute la fonction de rappel immédiatement après la création. 🎜rrreee🎜Lorsque la valeur de immediate est true, elle doit être exécutée immédiatement. Après avoir clarifié les exigences, améliorons l'écouteur watch. 🎜
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存在时,直接触发执行。

Quel est le principe de mise en œuvre de lécouteur Vue3

实现效果

Quel est le principe de mise en œuvre de lécouteur Vue3

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer