vue渲染页面的流程_Vue组件渲染的基本流程

本文详细阐述了Vue组件从编译template到渲染页面的过程,包括:1)vue-loader如何将template编译为render函数;2)组件的render函数生成VNode实例;3)VNode如何创建DOM并构建组件树;4)组件的初始化与更新;5)最终如何将DOM添加到页面。深入理解这些步骤有助于更好地掌握Vue的渲染机制。
摘要由CSDN通过智能技术生成

html:

组件渲染的基本过程

main.js:

import Vue from "vue";

import Home from "./home.vue";

new Vue({

el: "#app",

template: "",

components: { Home }

});

home.vue

{{text}}

export default {

name: "home",

data() {

return {

text: '测试'

};

},

mounted() {

},

methods: {

}

};

1.项目运行编译时,home.vue中的template会被vue-loader编译成render函数,并添加到组件选项对象里。组件通过components引用该组件选项,创建组件(Vue实例),渲染页面(组件被多次引用时,引用的是相同的组件选项,解释data为什么要是函数)。main.js根组件template转化成render函数是由vue插件中的编译器实现的。

// 编译器生成的render函数:

var render = function() {

var _vm = this

var _h = _vm.$createElement

var _c = _vm._self._c || _h

return _c(

"div",

{ staticClass: "home" },

[

_c("a",

[_vm._v(_vm._s(_vm.text))]

)

]

)

}

// 编译后的组件选项对象:

{

beforeCreate: []

beforeDestroy: []

data: function() {}

methods: {}

mounted: function() {}

name: "home"

render: function() {}

staticRenderFns: []

__file: "src/page/home/index.vue"

_compiled: true

}

2.组件渲染页面时会先调用render函数,render函数返回组件内标签节点(VNode实例)。每个标签(包括文本和组件标签等)会创建一个节点,先创建子标签的节点,再父节点创建时将它添加父节点的children数组中,形成与标签结构相同的树形结构。

// 虚拟节点构造函数

var VNode = function VNode (

tag,

data,

children,

text,

elm,

context,

componentOptions,

asyncFactory

) {

this.tag = tag;// 标签名

this.data = data;// 节点数据(原生事件信息),包括节点的钩子函数(包含标签上数据的钩子函数,例如:指令)

this.children = children;// 当节点是原生标签节点,保存它的子节点

this.text = text;// 为文本节点或者注释节点时的文本内容

this.elm = elm;// 节点对应的DOM,组件节点则对应的是该组件内原生根标签DOM

this.ns = undefined;

this.context = context;// 节点对应标签所在的组件(组件内包含该标签)

this.fnContext = undefined;

this.fnOptions = undefined;

this.fnScopeId = undefined;

this.key = data && data.key;// key值

this.componentOptions = componentOptions;// 缓存组件标签信息:包括组件名称,组件选项、标签上的props、标签上的事件、以及组件标签内的子节点。

this.componentInstance = undefined;// 组件标签节点对应的组件(Vue实例)

this.parent = undefined;// 当节点是组件内根标签节点,保存它的组件标签节点

this.raw = false;

this.isStatic = false;

this.isRootInsert = true;

this.isComment = false;

this.isCloned = false;

this.isOnce = false;

this.asyncFactory = asyncFactory;

this.asyncMeta = undefined;

this.isAsyncPlaceholder = false;

};

// 调用render函数,生成组件模板对应的节点

Vue.prototype._render = function () {

var vm = this;

var ref = vm.$options;

var render = ref.render;

var _parentVnode = ref._parentVnode;

...

vm.$vnode = _parentVnode;

// render self

var vnode;

...

currentRenderingInstance = vm;

vnode = render.call(vm._renderProxy, vm.$createElement);//返回组件模板形成节点(组件内根标签节点)

...

currentRenderingInstance = null;

...

vnode.parent = _parentVnode;// 设置组件根标签节点的parent为当前组件节点。

return vnode

};

// 根据标签创建节点

function _createElement (

context,

tag,

data,

children,

normalizationType

) {

if (isDef(data) && isDef((data).__ob__)) {

...

return createEmptyVNode()

}

...

if (!tag) {

return createEmptyVNode()

}

...

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

vnode = new VNode(

config.parsePlatformTagName(tag), data, children,

undefined, undefined, context

);

} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {/* 从组件实例option的components中寻找该标签对应的组件选项 */

vnode = createComponent(Ctor, data, context, children, tag);// 组件标签节点

} else {

vnode = new VNode(

tag, data, children,

undefined, undefined, context

);

}

} else {

vnode = createComponent(tag, data, context, children);// 标签节点

}

if (Array.isArray(vnode)) {

return vnode

} else if (isDef(vnode)) {

if (isDef(ns)) { applyNS(vnode, ns); }

if (isDef(data)) { registerDeepBindings(data); }

return vnode

} else {

return createEmptyVNode()

}

}

3.如果标签是组件标签,通过components获取的组件选项,并使用extend方法生成组件的构造函数,将构造函数和组件选项保存在组件标签节点上。

4.render函数生成组件内标签节点,并设置根节点的parent指向组件节点。将节点作为新节点,传入到patch方法中,组件页面初始更新时,不存在旧节点,直接根据新节点创建DOM。

// 组件页面更新

Vue.prototype._update = function (vnode, hydrating) {

var vm = this;

var prevEl = vm.$el;

var prevVnode = vm._vnode;

var restoreActiveInstance = setActiveInstance(vm);

vm._vnode = vnode;

...

if (!prevVnode) {// 组件初次渲染

// initial render

vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);// 将组件内根标签DOM赋值给实例的$el属性

} else {

// updates

vm.$el = vm.__patch__(prevVnode, vnode);// 将组件内根标签DOM赋值给实例的$el属性

}

restoreActiveInstance();

...

};

5.在patch方法中,根据节点创建DOM,并在节点上保存它的DOM引用,再根据节点的children值,创建子节点的DOM,再添加到父节点的DOM中,完成组件的渲染。

// 根据节点类型(原生标签或者组件标签),创建组件或者DOM

function createElm (

vnode,

insertedVnodeQueue,

parentElm,

refElm,

nested,

ownerArray,

index

) {

...

vnode.isRootInsert = !nested; // for transition enter check

if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {// 如果vnode是组件节点,创建组件

return

}

var data = vnode.data;

var children = vnode.children;

var tag = vnode.tag;

if (isDef(tag)) {

...

vnode.elm = vnode.ns // 创建DOM

? nodeOps.createElementNS(vnode.ns, tag)

: nodeOps.createElement(tag, vnode);

setScope(vnode);// 添加DOM属性,构造css作用域

{

createChildren(vnode, children, insertedVnodeQueue);// 创建子节点的DOM

if (isDef(data)) {

invokeCreateHooks(vnode, insertedVnodeQueue);

}

insert(parentElm, vnode.elm, refElm);// 添加父节点的DOM中

}

...

} else if (isTrue(vnode.isComment)) {// 注释节点

vnode.elm = nodeOps.createComment(vnode.text);

insert(parentElm, vnode.elm, refElm);// 添加DOM

} else {// 文本节点

vnode.elm = nodeOps.createTextNode(vnode.text);

insert(parentElm, vnode.elm, refElm);// 添加DOM

}

}

6.在根据节点创建DOM的过程中,如果节点包含组件构造器信息(即是组件节点),会先使用构造器创建组件,调用组件render方法,执行以上操作,生成组件内标签对应的节点,再根据节点生成DOM,并将根标签节点的DOM保存在组件上,然后添加到父节点的DOM上,完成组件的渲染。DOM添加到页面的过程是从下往上依次添加,DOM添加到父级DOM中,父级DOM添加到它的父级DOM中,迭代添加,最后将最上级的DOM添加到页面。

// 生成组件,并将组件内根标签DOM添加到父级DOM中:

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

var i = vnode.data;

if (isDef(i)) {

var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;

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

i(vnode, false /* hydrating */);// 调用节点钩子函数init,生成组件

}

if (isDef(vnode.componentInstance)) {

initComponent(vnode, insertedVnodeQueue);

insert(parentElm, vnode.elm, refElm);// 组件内根标签DOM添加到父级DOM中

if (isTrue(isReactivated)) {

reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);

}

return true

}

}

}

// 在节点钩子函数init创建组件

function init (vnode, hydrating) {

...

var child = vnode.componentInstance = createComponentInstanceForVnode(// 创建组件

vnode,

activeInstance// 父组件

);

child.$mount(hydrating ? vnode.elm : undefined, hydrating);//渲染页面

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值