vue源码分析——组件化

在上一篇文章中我介绍了 vue从实例化到渲染到页面的具体流程,本文基于该文章介绍组件的创建到渲染的具体流程,我们将从源码的⾓度来分析Vue的组件内部是如何⼯作的,只有了解了内部的⼯作原理,才能让我们使⽤它的时候更加得⼼应⼿。

vue源码相对比较复杂,需要耐心反复理解及调试,不懂就多调试问百度,罗马不是一日建成的,相信坚持就会有收获哈~

我们先写个测试页面,具体调试可以下载vue.js,然后具体做断点debugger调试。

  <script></script>

<div id="app"></div>

<script>

var Child = {

name: 'childcom',

template: '<div>{{msg}}</div>',

data() {

return {

msg: 'child msg',

}

}

}

var vm = new Vue({

el: '#app',

render(h){

return h(Child)

},

data: {

message: 'Hello',

}

})

</script>

先上图分析流程,绿线为组件化流程走向。

组件化

1. 执行render函数

由上一篇文章可知,页面渲染主要是执行vm._update(vm._render(), hydrating), 即先执行vm._render()函数获取vnode虚拟dom, 然后再vm._update(vnode)把虚拟dom更新到页面上,对应到实际开发中,也就是上面测试页中的 render(h){ return h(Child) }, 此处的 h 即为 createElement , createElement 经过参数重载之后调用 _createElement

_createElement 主要是根据tag,标签判断是组件还是普通node标签,返回对应的vnode虚拟dom,所以核心在于 createComponent 函数

function _createElement ( context, tag, data, children, normalizationType) {

if (typeof tag === 'string') {

// 普通元素 创建虚拟dom

vnode = new VNode(

config.parsePlatformTagName(tag), data, children,

undefined, undefined, context

);

}else{

// 组件 创建虚拟dom

vnode = createComponent(tag, data, context, children);

}

}

2. 执行 createComponent 函数

createComponent 的逻辑也会有⼀些复杂,但是分析源码⽐较推荐的是只分析核⼼流 程,分⽀流程可以之后针对性的看,所以这⾥针对组件渲染这3个关键步骤:

  • 创建 Vue.extend 函数

 // 在vue初始化initGlobalAPI的时候定义了  Vue.options._base = Vue

// baseCtor.extend(Ctor) 等价于 Vue.extend(Ctor)

const baseCtor = context.$options._base

if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) }

接着看 enxtend 函数是如何构建 VueComponent 构造函数的

 Vue.extend = function (extendOptions) {

// this 即为 Vue

var Super = this;

// 创建 VueComponent 构造函数

var Sub = function VueComponent (options) {

// 当我们去实例化 Sub 的时候,就会执⾏ this._init 逻辑再次⾛到了 Vue 实例的初始化逻辑

this._init(options);

};

// Object.create继承 Super, 也就是继承了 Vue

Sub.prototype = Object.create(Super.prototype);

// 更改constructor,指向自己,实现完美继承

Sub.prototype.constructor = Sub;

Sub.cid = cid++;

Sub.options = mergeOptions(

Super.options,

extendOptions

);

Sub['super'] = Super;

return Sub

}

可以学习通过Object.create的方式继承类,这也是面试中经常会遇到的问题。VueComponent 继承了 Vue, 然后合并options,最后返回 Sub 构造函数。

  • 安装组件钩⼦函数

// install component management hooks onto the placeholder node

installComponentHooks(data)

整个 installComponentHooks 的过程就是把 componentVNodeHooks 的钩⼦函数合并到 data.hook 中,在 VNode 执⾏ patch 的过程中执⾏相关的钩⼦函数,具体的执⾏我们稍后在介绍 patch 过程中会详细介绍。

  • 实例化 vnode

 // return a placeholder vnode

var name = Ctor.options.name || tag;

var vnode = new VNode(

("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),

data, undefined, undefined, undefined, context,

{Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children},

asyncFactory

);

return vnode

通过以上三个步骤可知组件的实例化是由Vue.extend中 VueComponent 构造函数实现的,并且createComponent最终返回了vnode,以执行 vm._update(vnode) 渲染页面。

3. 执行 _update 函数

_update 执行 patch ,也就是调用 path 函数

 function patch (oldVnode, vnode, hydrating, removeOnly) {

// 通过vnode创建新的真实dom节点

createElm(

vnode,

insertedVnodeQueue,

oldElm._leaveCb ? null : parentElm,

nodeOps.nextSibling(oldElm)

);

// 移除老节点

if (isDef(parentElm)) {

removeVnodes(parentElm, [oldVnode], 0, 0);

} else if (isDef(oldVnode.tag)) {

invokeDestroyHook(oldVnode);

}

}

path的核心就在于 createElm,通过该函数将虚拟dom创建为真实的dom,接下来看 createElm

  function createElm (vnode,insertedVnodeQueue, parentElm,refElm,nested,ownerArray,index

) {

// 如果是组件则执行 createComponent

if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {

return

}

// createChildren 递归调用 createElm 生成vnode

createChildren(vnode, children, insertedVnodeQueue);

if (isDef(data)) {

// 真实dom元素 属性构造

invokeCreateHooks(vnode, insertedVnodeQueue);

}

// 父元素插入真实dom元素

insert(parentElm, vnode.elm, refElm);

}

createComponent函数执行

let i = vnode.data

if (isDef(i = i.hook) && isDef(i = i.init)) {

i(vnode, false );

}

在第二步 createComponent 的时候, 通过执行 installComponentHooks(data), 将 vnode.data 挂上了 init 等钩子函数, i(vnode, false ); 也就是执行 下面的 init函数

var componentVNodeHooks = {

init: function init (vnode, hydrating) {

if (

vnode.componentInstance &&

!vnode.componentInstance._isDestroyed &&

vnode.data.keepAlive

) {

// kept-alive components, treat as a patch

var mountedNode = vnode; // work around flow

componentVNodeHooks.prepatch(mountedNode, mountedNode);

} else {

// 获取 Vue.extend 构造函数实例

var child = vnode.componentInstance = createComponentInstanceForVnode(

vnode,

activeInstance

);

// 挂载实例

child.$mount(hydrating ? vnode.elm : undefined, hydrating);

}

},

// ....

}

此处主要通过 createComponentInstanceForVnode 实例化 VueComponent 得到实例,并挂载。

function createComponentInstanceForVnode (

vnode, // we know it's MountedComponentVNode but flow doesn't

parent // activeInstance in lifecycle state

) {

var options = {

_isComponent: true,

_parentVnode: vnode,

parent: parent

};

// check inline-template render functions

var inlineTemplate = vnode.data.inlineTemplate;

if (isDef(inlineTemplate)) {

options.render = inlineTemplate.render;

options.staticRenderFns = inlineTemplate.staticRenderFns;

}

// vnode.componentOptions.Ctor 等价于 VueComponent

return new vnode.componentOptions.Ctor(options)

}

合并options 并把 _isComponent 设置为 true, 然后 new VueComponent(options),因为VueComponent是Vue的子类,那么实例化将重新执行一遍 Vue._init(options)。 也就再执行了一遍 vm._update(vm._render(), hydrating)。最后将执行回来 createElm, 还是继续把 createElm 代码贴出来。

  function createElm (vnode,insertedVnodeQueue, parentElm,refElm,nested,ownerArray,index

) {

// 如果是组件则执行 createComponent

if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {

return

}

// createChildren 递归调用 createElm 生成vnode

createChildren(vnode, children, insertedVnodeQueue);

if (isDef(data)) {

// 真实dom元素 属性构造

invokeCreateHooks(vnode, insertedVnodeQueue);

}

// 父元素插入真实dom元素

insert(parentElm, vnode.elm, refElm);

}

如果该组件没有子组件那么 createComponent(vnode, insertedVnodeQueue, parentElm, refElm)false, 那么将跳出递归一步步回到 patch 函数, 渲染页面。

其实,vue组件化过程就是先new Vue, 然后执行init初始化,接着判断是否含有组件, 如果没有则直接生成vode,最后通过path把vnode渲染到页面。如果有组件,则通过 createComponent函数,来创建 VueComponent构造函数 (继承 Vue)。然后在patch的时候通过 createElm函数中判断是否有组件,然后调用VueComponent函数,再执行一遍init初始化,直到没有组件被实例化完之后接着执行createElm来创建真实dom元素,最后把它渲染到页面上。具体可以参考上面的流程图来分析!

以上是 vue源码分析——组件化 的全部内容, 来源链接: utcz.com/a/40028.html

回到顶部