Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue2.x源码解析系列八:深入$mount内部理解组件挂载和更新原理 #31

Open
lihongxun945 opened this issue Aug 2, 2018 · 2 comments

Comments

@lihongxun945
Copy link
Owner

lihongxun945 commented Aug 2, 2018

回顾$mount 之前发生了什么

借用 Vue 官方这张经典的图来说明:

然我们回顾下在我们调用 $mount 挂载组件之前都发生了什么?,如上图所示,主要发生了这么些事情:

  • 构建 Vue 类,通过 initMixin, stateMixin等各种 xxxxMixinVue.prototype 上添加属性和方法,这在图中是没有显示的,因为这不是组件的生命周期,而是类的创建。
  • new Vue() 开始构建Vue实例,组件的生命周期在这里开始。
  • _init 函数中做 mergeOptions 合并配置,其中包括一些由Vue提供的默认配置比如 directives 等。图中并没有显示这一块
  • beforeCreate 阶段,还是在_init 函数中, 做 events,lifecycle 等初始化
  • create 阶段,做数据响应和数据注入。
  • template 编译为 render 函数,到这里就完成了 mount 的准备工作。

_init 结束之后,会调用 $mount 挂载组件,进入一个新的阶段 mount 阶段。那么,从 $mount 函数开始,到生成真实DOM,中间经历了哪些步骤呢?

Mount 开始

首先我们看 $mount 函数的定义:

platforms/web/runtime/index.js

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

可以看到它主要是调用了 mountComponent 函数,这个函数我们在之前讲到过,他会创建一个 Watcher 来监听 vm 的变动,一旦发生任何变化都会调用 vm._update 更新组件。mountComponent 核心的代码如下所示:

core/instance/lifecycle.js

    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

由于这里 lazy === false 所以在创建 watcher 的时候会立刻进行一次求值,也就是调用 vm._update(vm._render(), hydrating) 方法更新组件。这里的第一个参数是 vm._update 的结果,因此我们先看看这个函数做了什么:

core/instance/render.js

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
        // 省略
        vnode = vm._vnode
    }

    // set parent
    vnode.parent = _parentVnode
    return vnode
  }

上面的代码经过了我的简化。我们可以看到,_render 函数主要作用就是调用了 _renderProxy 生成 vnode,并返回,而 _renderProxy 其实就是调用了我们编译好的 render 函数。所以我们知道了,每当组件有任何更新的时候,都会调用 render 函数生成新的 vnode。那么我们再看 updateComponent 函数,当组件更新的时候就变成了这样:

vm._update(vnode, hydrating)

因为我们更新了 vnode ,这个 _update 函数的目的显然是会进行 patch ,把更新同步到真实的 DOM 上。来看看 _update 函数的代码:

core/instance/lifecycle.js

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // 省略
  }

正如我们所料,_update 函数主要做的事情就是调用了 __patch__ 方法。 __patch__ 方法会负责把 vnode 渲染为真实的 DOM.我画了一个图,来表示整个 $mount 以及 update 的过程:

$mount 函数其实是创建了上面这个流程,而不是发起了一次 渲染,因为在创建watcher的时候 lazy === false 所以创建完成之后立刻会触发一次更新。

图中的两个重要阶段 renderpatch 我们分别在接下来的两章详细讲解。

下一章,让我们看看 render 函数是如何生成 vnode 的。

下一章:Vue2.x源码解析系列九:vnode的生成与更新机制

@hulahulahhh
Copy link

既然是在$mount中编译模版、初始化watcher,那为何在created勾子中改变数据的值,也会触发相应的更新?这个时候watcher不是还没有初始化吗

@qk44077907
Copy link

既然是在$mount中编译模版、初始化watcher,那为何在created勾子中改变数据的值,也会触发相应的更新?这个时候watcher不是还没有初始化吗

render watcher没有初始化,其他的watcher如watch和computed选项已经初始化了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants