Vue.js 反应性如何在幕后工作? [英] How does Vue.js reactivity work under the hood?

查看:29
本文介绍了Vue.js 反应性如何在幕后工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基本上,当我有一个组件时,我们称它为TransportComponenet.vue",在该组件中,我有一个 data(),我的属性是 carId、transportId.vue 所做的是为这些属性创建 getter 和 setter.假设在这个组件的视图中,我输入 {{carId + transportId}}{{carId * transportId}}.

Basically, when I have a component, let’s call it "TransportComponenet.vue", and in that component, I have a data() and My properties are carId, transportId. What vue does is makes getters and setters for these properties. Let’s say in this componenet’s view, I type {{carId + transportId}} and also {{carId * transportId}}.

据我所知,Vue 会访问我的视图,查看它们,以及我有 getter 的任何地方({{carId+ transportId}}{{carId * transportId}} ) 是吸气剂.所以 vue 来了,并将它们注册到组件的 watcher 中.当我在某处使用诸如 this.carId = 5 之类的 setter 时.Vue 为此属性执行 setter 函数,并重新评估之前保存在 watcher 中的函数(getter).这是正确的假设吗?

As far As I know, Vue comes to my views, looks at them, and wherever I have getters ( {{carId+ transportId}} or {{carId * transportId}} ) are getters. So vue comes and registers them in component’s watcher. When I somewhere use setters such as this.carId = 5. Vue does the setter function for this property and reevalutes the functions (getters) that were saved before in the watcher. Is this the correct assumption?

我不明白的是,Dep 类和 Watcher 类之间存在什么关系?我知道他们都扮演着重要的角色.我真的很尊重什么东西去哪里、什么时候以及为什么去"的整个解释.

What I don’t get is what relationship does exist between Dep class and Watcher class ? I know they both play the big role. I’d really honor the whole explanation "which thing goes where and when and why".

推荐答案

Reactivity 是状态和 DOM 之间的自动同步.这就是像 Vue 和 React 这样的视图库试图在它们的核心中做的事情.他们以自己的方式做到这一点.

Reactivity is automatic synchronization between the state and the DOM. That's what the view libraries like Vue and React try to do in their core. They do that in their own ways.

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

I see Vue's reactivity system as being two fold. One side of the coin is the DOM update mechanism. Let's look into that first.

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

Let's say you have a component with a template like:

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

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

这个模板被转换成渲染函数.这发生在使用 vue-loader 的构建期间.上面模板的渲染函数类似于:

This template gets converted into render function. This happens during build time using vue-loader. The render function for the template above looks something like:

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

Render 函数在浏览器上运行,执行时返回一个 Vnode(虚拟节点).虚拟节点只是一个简单的 JavaScript 对象,它代表了实际的 DOM 节点,即 DOM 节点的蓝图.上面的渲染函数在执行时会返回如下内容:

Render function runs on the browser and when executed returns a Vnode (virtual node). A virtual node is just a simple JavaScript object that represents the actual DOM node, a blueprint for the DOM node. The above render function when executed would return something like:

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

Vue 然后根据这个 Vnode 蓝图创建实际的 DOM 节点并将其放入 DOM.

Vue then creates actual DOM node from this Vnode blueprint and places it into the DOM.

稍后,假设 foo 的值发生变化,并且 不知何故 渲染函数再次运行.它将提供不同的 Vnode.然后 Vue 将新的 Vnode 与旧的 Vnode 进行比较,并仅将必要的更改修补到 DOM 中.

Later, let's say the foo's value changes and somehow the render function runs again. It will give a different Vnode. Vue then diffs the new Vnode with the old one and patches only the necessary changes required into the DOM.

这为我们提供了一种机制来有效地更新 DOM,从而获取组件的最新状态.如果每次组件的渲染函数在其任何状态(数据、道具等)发生变化时都被调用,我们就有了完整的反应式系统.

This gives us a mechanism to update the DOM efficiently taking the latest state of things for a component. If every time the render function of a component gets called when any of its state (data, props etc) changes, we have the full reactivity system.

这就是 Vue 反应性硬币的另一面.那就是反应性 getter 和 setter.

That's where the other side of Vue's reactivity coin comes in. And that is the reactive getters and setters.

这是了解的好时机Object.defineProperty API,如果您还不知道的话.因为 Vue 的反应系统依赖于这个 API.

This will be a good time to understand Object.defineProperty API if you are not already aware of that. Because Vue's reactivity system relies on this API.

TLDR;它允许我们使用我们自己的 getter 和 setter 函数来覆盖对象的属性访问和赋值.

TLDR; it allows us to override an object's property access and assignment with our own getter and setter functions.

当 Vue 实例化您的组件时,它会遍历您的 数据props 并使用 Object.defineProperty 重新定义它们.

When Vue instantiates your component, it walks through all the properties of your data and props and redefines them using Object.defineProperty.

它实际上做的是,它定义每个 data 和 props 属性的 getter 和 setter.通过这样做,它会覆盖该属性的点访问 (this.data.foo) 和赋值 (this.data.foo = someNewValue).因此,每当在该属性上发生这两个操作时,就会调用我们的覆盖.所以我们有一个钩子可以对它们做些什么.我们稍后会回到这个话题.

What it actually does is, it defines getters and setters for each of the data and props properties. By doing so, it overrides the dot access (this.data.foo) and the assignment (this.data.foo = someNewValue) of that property. So whenever these two actions occur on that property, our overrides get invoked. So we have a hook to do something about them. We will get back to this in a bit.

此外,对于每个属性都有一个 新的 Dep() 类实例被创建.之所以称为Dep,是因为每个数据或道具属性都可以是组件渲染函数的dependency.

Also, for each property a new Dep() class instance is created. It's called Dep because each data or props property can be a dependency to the component's render function.

但首先,重要的是要知道每个组件的渲染函数都会被调用 在观察者内.所以观察者有一个关联组件的渲染函数.Watcher 也用于其他目的,但是当它观察组件的渲染功能时,它是一个 渲染观察者.观察者将自己指定为 当前运行的观察者,某处可全局访问(在 Dep.target 静态属性),然后运行组件的 渲染函数.

But first, it's important to know that each component's render function gets invoked within a watcher. So a watcher has an associated component's render function with it. Watcher is used for other purposes as well, but when it is watching a component's render function, it is a render watcher. The watcher assigns itself as the current running watcher, somewhere accessible globally (in Dep.target static property), and then runs the component's render function.

现在我们回到反应式 getter 和 setter.当您运行渲染函数时,将访问状态属性.例如.this.data.foo.这会调用我们的 getter 覆盖.当 getter 被调用时,dep.depend() 被调用.这会检查 Dep.target 中是否分配了当前正在运行的观察者,如果是,则将该观察者分配为该 dep 对象的订阅者.它被称为 dep.depend() 因为我们让 watcher 依赖于 dep.

Now we get back to the reactive getters and setters. When you run the render function, the state properties are accessed. E.g. this.data.foo. This invokes our getter override. When the getter is invoked, dep.depend() is called. This checks if there is a current running watcher assigned in Dep.target, and if so, it assigns that watcher as the subscriber of this dep object. It's called dep.depend() because we are making the watcher depend on the dep.

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

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

稍后,当 state 属性被更新时,setter 被调用,关联的 dep 对象将新值通知其订阅者.订阅者是感知渲染函数的观察者,这就是组件渲染函数在其状态改变时自动调用的方式.

Later, when the state property gets updated, the setter is invoked and the associated dep object notifies its subscribers about the new value. The subscribers are the watchers which are render function aware and that's how the components render function gets invoked automatically when its state changes.

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

This makes the reactivity system complete. We have a way to call a component's render function whenever its state changes. And we have a way to efficiently update the DOM once that happens.

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

This way Vue has created a relationship between a state property and a render function. Vue knows exactly which render function to execute when a state property changes. This scales up really well and basically removes a category of performance optimization responsibility from the hands of developer. Devs don't need to worry about over rendering of components no matter how big the component tree. To prevent this, React e.g. provides PureComponent or shouldComponentUpdate. In Vue, this is just not necessary since Vue knows exactly which component to re-render when any state changes.

但是现在我们知道了 Vue 是如何让事情变得反应式的,我们可以想办法稍微优化一下.假设您有一个博客文章组件.您从后端获取一些数据并使用 Vue 组件将它们显示在浏览器上.但是博客数据不需要是被动的,因为它很可能不会改变.在这种情况下,我们可以告诉 Vue 跳过通过冻结对象来使此类数据具有反应性.

But now that we know how Vue makes things reactive, we can think of a way to optimize things a bit. Imagine you have a blog post component. You get some data from the backend and show them on the browser using Vue component. But there is no need for the blog data to be reactive because it most likely won't change. In such situation, we can tell Vue to skip making such data reactive by freezing the objects.

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

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

Oject.freeze其他事情会禁用对象的可配置性.您不能使用 Object.defineProperty 再次重新定义该对象的属性.所以 Vue 跳过整个反应性设置为这些对象工作.

Oject.freeze among other things disables the configurability of an object. You cannot redefine the properties of that object again using Object.defineProperty. So Vue skips the whole reactivity setup work for such objects.

此外,您可以自己阅读 Vue 源代码,关于此主题还有两个非常好的资源:

Besides, going through the Vue source code yourself, there are two extremely good resources available on this topic:

  1. Vue Mastery 的高级组件课程
  2. FrontendMaster 的 从头开始的高级 Vue.js 功能 作者 Evan You
  1. Vue Mastery's Advanced component course
  2. FrontendMaster's Advanced Vue.js Features from the Ground Up by Evan You

如果您对简单的虚拟 DOM 实现的内部结构感到好奇,请查看 Jason Yu 的博文.

If you are curious about the internals of a simple virtual DOM implementation, check out the blog post by Jason Yu.

从头开始构建一个简单的虚拟 DOM

这篇关于Vue.js 反应性如何在幕后工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆