react 性是状态和DOM之间的自动同步.这就是Vue和React等视图库在其核心中所做的.他们用自己的方式做这件事.
我认为Vue的react 系统是双重的.问题的一面是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(虚拟 node ).虚拟 node 只是一个简单的JavaScript对象,它代表实际的DOM node ,是DOM node 的蓝图.执行上述渲染函数时,会返回如下内容:
{
tag: 'div',
children: ['bar']
}
Vue然后根据这个Vnode蓝图创建实际的DOM node ,并将其放入DOM中.
稍后,假设foo
的值发生变化,somehow渲染函数再次运行.它将给出一个不同的Vnode.Vue然后将新的Vnode与旧的Vnode区分开来,只将所需的更改修补到DOM中.
这为我们提供了一种机制,可以根据组件的最新状态高效地更新DOM.如果组件的任何状态(数据、props 等)发生变化时,每次调用组件的渲染函数时,我们都有完整的react 系统.
这就是Vuereact 性硬币的另一面.这就是react 型的接受者和接受者.
如果你还没有意识到这一点,这将是理解Object.defineProperty API的好时机.因为Vue的react 系统依赖于这个API.
太长,读不下go 了它允许我们用自己的getter和setter函数覆盖对象的属性访问和赋值.
当Vue实例化组件时,它会遍历data和props的所有属性,并使用Object.defineProperty
重新定义它们.
它实际做的是,每个数据和props 属性都有defines getters and setters个.通过这样做,它会覆盖该属性的点访问(this.data.foo)和赋值(this.data.foo=someNewValue).因此,每当这两个操作在该属性上发生时,就会调用我们的重写.所以我们有一个钩子来对付他们.我们稍后会回到这个话题.
此外, for each 属性创建一个new Dep()类实例.之所以称之为Dep
,是因 for each 数据或props 属性都可以是组件渲染函数的dep属性.
但首先,重要的是要知道每个组件的渲染函数都会被调用within a watcher次.因此,观察者有一个相关组件的渲染功能.Watcher也可用于其他用途,但当它查看组件的渲染函数时,它是render watcher.观察者将自己指定为current running watcher,在某个地方可以全局访问(在Dep.target静态属性中),然后运行组件的render function.
现在我们回到被动的接球手和接球手.运行渲染函数时,将访问状态属性.例如this.data.foo
.这将调用我们的getter覆盖.调用getter时,会调用dep.depend()
.dep判断当前的watcher,如果这个watcher正在运行,那么这个watcher将被分配给当前的watcher.它被称为dep.depend()
,因为我们让watcher
依赖于dep
.
_______________ _______________
| | | |
| | subscribes to | |
| Watcher | --------------> | Dep |
| | | |
|_____________| |_____________|
这和
_______________ _______________
| | | |
| Component | subscribes to | it's |
| render | --------------> | state |
| function | | property |
|_____________| |_____________|
稍后,当state属性更新时,会调用setter,关联的dep对象会将新值通知其订阅者.订阅者是观察者,可以感知呈现函数,这就是组件呈现函数在状态改变时自动被调用的方式.
这使得react 系统完整.我们有一种方法可以在组件的状态发生变化时调用其渲染函数.我们有一种方法可以在这种情况下高效地更新DOM.
通过这种方式,Vue在状态属性和渲染函数之间创建了一种关系.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整个react 性设置都适用于这样的物体.
此外,您自己也可以浏览Vue源代码,在这个主题上有两个非常好的资源:
- Vue Mastery的Advanced component门课程
- 由Evan You创作的FrontendMaster's Advanced Vue.js Features from the Ground Up
如果您对一个简单的虚拟DOM实现的内部 struct 感到好奇,请查看Jason Yu的博客文章.
Building a Simple Virtual DOM from Scratch