[基本上,当我有一个组件时,我们将其称为“ TransportComponenet.vue”,在该组件中,我有一个data(),而我的属性是carId,transportId。 vue所做的是使这些属性具有吸气剂和吸气剂。假设在此组件视图中,我键入{{carId + transportId}}
,也键入{{carId * transportId}}
。
据我所知,Vue进入了我的视图,看着它们,无论我在哪里使用吸气剂({{carId+ transportId}}
或{{carId * transportId}}
)都是吸气剂。因此,vue来了并将它们注册在组件的观察器中。当我在某处使用诸如this.carId = 5
之类的设置器时。 Vue为此属性执行setter函数,并重新评估先前在监视程序中保存的函数(getter)。这是正确的假设吗?
我不知道Dep类和Watcher类之间存在什么关系?我知道他们俩都扮演着重要角色。我真的很乐意整个解释“哪件事发生在哪里,何时何地以及为什么”。
反应性是状态和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实例化组件时,它将遍历data和props的所有属性,并使用Object.defineProperty
重新定义它们。
实际上,它是每个data和props属性的defines getters and setters。这样,它将覆盖该属性的点访问(this.data.foo)和分配(this.data.foo = someNewValue)。因此,只要这两个动作发生在该属性上,我们的覆盖就会被调用。因此,我们有办法对它们进行处理。我们将稍等一下。
此外,还为每个属性创建了一个new Dep()类实例。之所以称为Dep
,是因为每个data或props属性可以是组件的render函数的dep
但是首先,重要的是要知道每个组件的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源代码,关于此主题有两个非常好的资源:
如果您对简单的虚拟DOM实现的内部结构感到好奇,请查看Jason Yu的博客文章。