组件化是 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 树插入到父元素之中。到这里自定义组件的渲染过程就结束了。