Plongée profonde dans les principes réactifs


Table des matières


Comment suivre les modifications


Lorsque vous transmettez un objet JavaScript normal dans une instance de Vue en tant qu'option data, Vue parcourra toutes les propriétés de cet objet et utilisera le Object.defineProperty Convertissez toutes ces propriétés en getter/setterdata 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

1568871314181366.png


检测变化的注意事项


受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

var vm = new Vue({
  data:{
    a:1
  }
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。例如,对于:

Vue.set(vm.someObject, 'b', 2)

您还可以使用 vm.$set 实例方法,这也是全局 Vue.set. Object.defineProperty est une fonctionnalité d'ES5 qui ne peut pas être shimmée, c'est pourquoi Vue ne prend pas en charge IE8 et les navigateurs inférieurs.

🎜Ces getters/setters sont invisibles pour l'utilisateur, mais en interne, ils permettent à Vue de suivre les dépendances et de notifier les changements lorsque les propriétés sont accédées et modifiées. Ce qu'il faut noter ici, c'est que différents navigateurs formatent différemment les getters/setters lors de l'impression d'objets de données sur la console, il est donc recommandé d'installer vue-devtools🎜 pour obtenir une interface plus conviviale pour inspecter les données. 🎜🎜Chaque instance de composant correspond à une instance 🎜watcher🎜, qui enregistre les attributs de données "touchés" en tant que dépendances pendant le processus de rendu du composant. Plus tard, lorsque le setter de la dépendance se déclenche, l'observateur en est informé, provoquant le nouveau rendu de son composant associé. 🎜

1568871314181366.png🎜🎜🎜🎜

🎜🎜Remarques sur la détection des changements🎜🎜🎜🎜🎜Limité par le JavaScript moderne (et Object.observe est également obsolète), Vue 🎜 ne peut pas détecter les propriétés des objets Ajouter ou supprimer 🎜 . Étant donné que Vue effectue une conversion getter/setter sur les propriétés lors de l'initialisation de l'instance, la propriété doit exister sur l'objet data pour que Vue la convertisse en réactif. Par exemple : 🎜🎜
this.$set(this.someObject,'b',2)
🎜Vue ne permet pas d'ajouter dynamiquement des propriétés réactives au niveau racine aux instances déjà créées. Cependant, vous pouvez ajouter des propriétés réactives aux objets imbriqués à l'aide de la méthode Vue.set(object, propertyName, value). Par exemple, pour : 🎜
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
🎜 Vous pouvez également utiliser la méthode d'instance vm.$set, qui est également un alias pour la méthode globale Vue.set : 🎜
var vm = new Vue({
  data: {
    // 声明 message 为一个空值字符串
    message: ''
  },
  template: '<div>{{ message }}</div>'
})
// 之后设置 `message`
vm.message = 'Hello!'

Parfois, vous devrez peut-être attribuer plusieurs nouvelles propriétés à un objet existant, par exemple en utilisant Object.assign() ou _.extend(). Cependant, les nouvelles propriétés ajoutées à l'objet de cette manière ne déclencheront pas de mise à jour. Dans ce cas, vous devez créer un nouvel objet à partir de l'objet d'origine ainsi que les propriétés de l'objet dans lequel vous souhaitez vous fondre. Object.assign()_.extend()。但是,这样添加到对象上的新属性不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的属性一起创建一个新的对象。

<div id="example">{{message}}</div>

也有一些数组相关的注意事项,之前已经在列表渲染中讲过。


声明响应式属性


由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值:

var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

如果你未在 data 选项中声明 message,Vue 将警告你渲染函数正在试图访问不存在的属性。

这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data 对象就像组件状态的结构 (schema)。提前声明所有的响应式属性,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。


异步更新队列


可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

Il existe également certaines considérations liées aux tableaux, qui ont déjà été abordées dans le Rendu de liste Passer. 🎜🎜
🎜

Déclarer les attributs réactifs

< hr />🎜Étant donné que Vue n'autorise pas l'ajout dynamique de propriétés réactives au niveau racine, vous devez déclarer toutes les propriétés réactives au niveau racine avant d'initialiser l'instance, même s'il s'agit simplement d'une valeur nulle :
🎜
methods: {
  updateMessage: async function () {
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}
🎜Si vous êtes pas dans message dans l'option >data, Vue vous avertira que la fonction de rendu tente d'accéder à une propriété inexistante. 🎜🎜Il y a des raisons techniques derrière cette restriction. Elle élimine un type de cas limite dans le système de suivi des dépendances et permet également aux instances Vue de mieux fonctionner avec le système de vérification de type. Mais en même temps, il y a une considération importante en termes de maintenabilité du code : les objets data sont comme le schéma de l'état du composant. La déclaration initiale de toutes les propriétés réactives rend le code du composant plus facile à comprendre lorsqu'il sera modifié ultérieurement ou lu par d'autres développeurs. 🎜🎜
🎜

File d'attente de mise à jour asynchrone


🎜Peut-être n'avez-vous pas remarqué que Vue s'exécute de manière asynchrone lors de la mise à jour du DOM. Tant qu'il écoute les modifications de données, Vue ouvrira une file d'attente et mettra en mémoire tampon toutes les modifications de données qui se produisent dans la même boucle d'événements. Si le même observateur est déclenché plusieurs fois, il ne sera placé qu’une seule fois dans la file d’attente. Cette déduplication lors de la mise en mémoire tampon est importante pour éviter les calculs et opérations DOM inutiles. Ensuite, lors de la prochaine boucle d'événement "tick", Vue vide la file d'attente et effectue le travail réel (dédupliqué). Vue essaie en interne d'utiliser Promise.then natif, MutationObserver et setImmediate pour les files d'attente asynchrones si l'environnement d'exécution ne le prend pas en charge, . setTimeout(fn, 0) à la place. 🎜🎜Par exemple, lorsque vous définissez vm.someData = 'new value', le composant ne sera pas restitué immédiatement. Lorsque la file d'attente est vidée, le composant est mis à jour au prochain « tick » de la boucle d'événement. La plupart du temps, nous n'avons pas à nous soucier de ce processus, mais si vous souhaitez faire quelque chose basé sur l'état DOM mis à jour, cela peut être un peu délicat. Alors que Vue.js encourage généralement les développeurs à penser de manière « basée sur les données » et à éviter tout contact direct avec le DOM, nous devons parfois le faire. Pour attendre que Vue ait fini de mettre à jour le DOM après les modifications des données, vous pouvez utiliser Vue.nextTick(callback) immédiatement après les modifications des données. De cette façon, la fonction de rappel sera appelée une fois la mise à jour du DOM terminée. Par exemple : 🎜rrreeerrreee

Il est particulièrement pratique d'utiliser la méthode d'instance vm.$nextTick() dans un composant car elle ne nécessite pas de Vue globale, et this dans la fonction de rappel > Sera automatiquement lié à l'instance Vue actuelle : vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

rrreee

因为 $nextTick() 返回一个 PromiserrreeeParce que $nextTick() renvoie un objet Promise, vous pouvez donc utiliser le nouveau ES2016 async/await

La syntaxe accomplit la même chose :

rrreee

🎜