组件化是 Vue, React 等这些框架的一个核心思想,通过把页面拆成一个个高内聚、低耦合的组件,可以极大程度提高我们的代码复用度,同时也使得项目更加易于维护。所以,本文就来分析下组件的渲染流程。我们通过下面这个例子来进行分析:
1 | <div id="demo"> |
这里我们分为两步来分析:组件声明、组件创建及渲染
组件声明
首先,我们看下 Vue.component 是什么东西,它的声明在 core/global-api/assets.js:
1 | export function initAssetRegisters(Vue: GlobalAPI) { |
这里 this.options._base.extend(definition) 调用的其实就是 Vue.extend(definition):
1 | Vue.extend = function (extendOptions: Object): Function { |
这里我们可以理解为返回了一个名叫 VueComponent 的构造函数且继承了 Vue。所以,这里的组件定义完成后 Vue 就会变成这样:
1 | { |
组件创建及挂载
我们知道 Vue 中的模板最后会变编译成 render 函数,比如上面例子最终的 render 函数会如下所示:
1 | render() { |
这里 _c 的定义可以在 core/instance/render.js 中找到:
1 | vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) |
所以 _c('comp') 最终还是调用了 createElement (core/vdom/create-element.js) 这个方法:
1 | export function createElement ( |
这里我们只看自定义组件的相关逻辑,发现最后调用了 createComponent (core/vdom/create-component.js):
1 | export function createComponent ( |
这里我们跳过其他的代码,先看看 installComponentHooks:
1 | function installComponentHooks(data: VNodeData) { |
这里会在 data.hook 上挂载一些 hooks,如果用户也传了相同的 hooks 则会进行合并。这个 hooks 又是啥呢:
1 | const componentVNodeHooks = { |
这里有四个 hooks ,看他们的名字就知道他们会在对应的操作去执行。比如 init 会在组件初始化的时候执行,这个后面碰到了再说。我们继续看 createComponent:
1 | // return a placeholder vnode |
1 | export default class VNode { |
这里初始化了一个 VNode 并进行了返回,到这里 _c('comp') 的任务就完成了。可以看到我们的自定义组件的构造函数在这一步并没有执行,仅仅只是挂载到了 componentOptions 属性上。那他什么时候执行呢?别急,我们接着往下走。
当根组件的 render 执行完后,会执行 vm._update 进行组件的更新,然后会调用 __patch__,我们顺藤摸瓜最终来到 core/vdom/patch.js:
1 | return function patch(oldVnode, vnode, hydrating, removeOnly) { |
然后会走到 createElm:
1 | function createElm( |
注意到这里的 vnode 是 <div id="demo"></div> 这个元素的,所以会走到 createChildren:
1 | function createChildren(vnode, children, insertedVnodeQueue) { |
这里最后又回到了 createElm,不过此时的 vnode 就是自定义组件了,会走到这里:
1 | function createElm( |
1 | function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) { |
注意到这里会执行 i.init 方法,该方法上文已经说过,会实例化组件对象,然后进行 $mount。而执行 $mount 最终又会走到 patch 方法,并最终执行 createElm:
1 | function patch(oldVnode, vnode, hydrating, removeOnly) { |
执行该方法又会递归的将自定义组件内的 vnode 渲染成真实的 dom,最后通过 insert 方法将整颗 dom 树插入到父元素之中。到这里自定义组件的渲染过程就结束了。