Vue.js反应性如何在后台运行?

问题描述 投票:4回答:1

[基本上,当我有一个组件时,我们将其称为“ TransportComponenet.vue”,在该组件中,我有一个data(),而我的属性是carId,transportId。 vue所做的是使这些属性具有吸气剂和吸气剂。假设在此组件视图中,我键入{{carId + transportId}},也键入{{carId * transportId}}

据我所知,Vue进入了我的视图,看着它们,无论我在哪里使用吸气剂({{carId+ transportId}}{{carId * transportId}})都是吸气剂。因此,vue来了并将它们注册在组件的观察器中。当我在某处使用诸如this.carId = 5之类的设置器时。 Vue为此属性执行setter函数,并重新评估先前在监视程序中保存的函数(getter)。这是正确的假设吗?

我不知道Dep类和Watcher类之间存在什么关系?我知道他们俩都扮演着重要角色。我真的很乐意整个解释“哪件事发生在哪里,何时何地以及为什么”。

vue.js vuejs2 vue-component
1个回答
14
投票

反应性是状态和DOM之间的自动同步。这就是像Vue和React这样的视图库试图在其核心中完成的工作。他们以自己的方式做到这一点。

我认为Vue的反应系统是两层。硬币的一面是DOM更新机制。让我们先研究一下。

假设您有一个带有模板的组件,例如:

<template>
    <div>{{ foo }}</div>
</template>

<script>
export default {
    data() {
        return {foo: 'bar'};
    }
}
</script>

此模板被转换为渲染功能。这在使用vue-loader的构建期间发生。上面模板的渲染功能看起来像:

function anonymous(
) {
    with(this){return _c('div',[_v(_s(foo))])}
}

Render函数在浏览器上运行,执行后将返回Vnode(虚拟节点)。虚拟节点只是代表实际DOM节点的简单JavaScript对象,这是DOM节点的蓝图。上面的render函数在执行时将返回类似:

{
    tag: 'div',
    children: ['bar']
}

Vue然后根据此Vnode蓝图创建实际的DOM节点,并将其放入DOM。

稍后,假设foo的值发生更改,并且以某种方式渲染功能再次运行。它将给出一个不同的Vnode。然后,Vue将新的Vnode与旧的Vnode进行比较,并仅将所需的必要更改修补到DOM中。

这为我们提供了一种以组件的最新状态有效地更新DOM的机制。如果每次在组件的任何状态(数据,道具等)发生变化时调用组件的render函数,我们都有完整的反应系统。

这就是Vue反应性硬币的另一面。这就是反应性吸气剂和吸气剂。

如果您还不了解Object.defineProperty API,现在是个好时机。因为Vue的反应性系统依赖于此API。

TLDR;它允许我们使用自己的getter和setter函数覆盖对象的属性访问和分配。

当Vue实例化组件时,它将遍历dataprops的所有属性,并使用Object.defineProperty重新定义它们。

实际上,它是每个data和props属性的defines getters and setters。这样,它将覆盖该属性的点访问(this.data.foo)和分配(this.data.foo = someNewValue)。因此,只要这两个动作发生在该属性上,我们的覆盖就会被调用。因此,我们有办法对它们进行处理。我们将稍等一下。

此外,还为每个属性创建了一个new Dep()类实例。之所以称为Dep,是因为每个data或props属性可以是组件的render函数的dep

endency。

但是首先,重要的是要知道每个组件的render函数都被调用within a watcher。因此,观察者具有与之关联的组件的render函数。监视程序也用于其他目的,但是当监视组件的渲染功能时,它是render watcher。监视程序将自己分配为current running watcher,该位置可全局访问(在Dep.target静态属性中),然后运行组件的render function

现在我们回到反应性吸气剂和吸气剂。运行渲染功能时,将访问状态属性。例如。 this.data.foo。这将调用我们的getter覆盖。调用getter时,将调用dep.depend()。这将检查Dep.target中是否分配了当前正在运行的监视程序,如果存在,则将其指定为该dep对象的订户。之所以称为dep.depend(),是因为我们使watcher取决于dep

_______________                       _______________
|             |                       |             |
|             |     subscribes to     |             |
|   Watcher   |    -------------->    |     Dep     |
|             |                       |             |
|_____________|                       |_____________|

与]相同>

_______________                       _______________
|             |                       |             |
|  Component  |     subscribes to     |   it's      |
|  render     |    -------------->    |   state     |
|  function   |                       |   property  |
|_____________|                       |_____________|

稍后,当状态属性得到更新时,将调用setter,并且关联的dep对象将其新值通知其订户。订阅者是观察者,他们了解渲染功能,这就是组件渲染功能在其状态更改时如何自动调用的方式。

这使反应系统完整。我们有一种方法可以在状态改变时调用组件的render函数。一旦发生这种情况,我们就有一种有效地更新DOM的方法。

这样,Vue在state属性和render函数之间创建了关系。当状态属性更改时,Vue确切知道要执行哪个渲染函数。这确实可以很好地扩展,并且基本上消除了开发人员手中的性能优化责任。无论组件树有多大,开发人员都不必担心组件的过度渲染。为了防止这种情况,请使用React提供PureComponent或shouldComponentUpdate。在Vue中,这是没有必要的,因为当任何状态发生变化时,Vue都会确切地知道要重新渲染哪个组件。

但是现在我们知道Vue如何使事物具有反应性,我们可以考虑一种优化事物的方法。假设您有一个博客文章组件。您可以从后端获取一些数据,并使用Vue组件在浏览器中显示它们。但是,无需使博客数据具有响应性,因为它很可能不会更改。在这种情况下,我们可以告诉Vue通过冻结对象来跳过使此类数据具有反应性。

export default {
  data: () => ({
    list: {}
  }),
  async created() {
    const list = await this.someHttpClient.get('/some-list');

    this.list = Object.freeze(list);
  }
};

Oject.freeze除其他外,禁用对象的可配置性。您无法使用Object.defineProperty重新定义该对象的属性。因此,Vue skips会为此类对象完成整个反应性设置工作。

此外,您自己浏览Vue源代码,关于此主题有两个非常好的资源:

  1. Vue Mastery的Advanced component课程
  2. Evan You的[FrontendMaster的Advanced Vue.js Features from the Ground Up
  3. 如果您对简单的虚拟DOM实现的内部结构感到好奇,请查看Jason Yu的博客文章。

    Building a Simple Virtual DOM from Scratch