浅谈Vue页面级缓存解决方案feb(2)

let Foo = { template: '<div>foo component</div>', name: 'Foo' } let Bar = { template: '<div>bar component</div>', name: 'Bar' } let gvm = new Vue({ el: '#app', template: ` <div> <keep-alive> <component :is="renderCom"></component> </keep-alive> <button @click="change">切换组件</button> </div> `, components: { Foo, Bar }, data: { renderCom: 'Foo' }, methods: { change () { this.renderCom = this.renderCom === 'Foo' ? 'Bar': 'Foo' } } })

上面例子中,根实例的template会被编译成如下render函数

function anonymous( ) { with(this){return _c('div',{attrs:{"id":"#app"}},[_c('keep-alive',[_c(renderCom,{tag:"component"})],1),_c('button',{on:{"click":change}})],1)} }

可使用在线编译:

根据上面的render函数可以知道,vnode生成的过程是深度递归的,先创建子元素的vnode再创建父元素的vnode。
所以首次渲染的时候,在生成keep-alive组件vnode的时候,Foo组件的vnode已经生成好了,并且作为keep-alive组件vnode构造函数(_c)的参数传入。

_c('keep-alive',[_c(renderCom,{tag:"component"})

生成的keep-alive组件的vnode如下

{ tag: 'vue-component-2-keep-alive', ... children: undefined, componentInstance: undefined, componentOptions: { Ctor: f VueComponent(options), children: [Vnode], listeners: undefined, propsData: {}, tag: 'keep-alive' }, context: Vue {...}, // 调用 $createElement/_c的组件实例, 此处是根组件实例对象 data: { hook: { init: f, prepatch: f, insert: f, destroy: f } } }

此处需要注意组件的vnode是没有children的,而是将原本的children作为vnode的componentOptions的children属性,componentOptions在组件实例化的时候会被用到,同时在初始化的时候componentOptions.children最终会赋值给vm.$slots,源码部分如下

// createComponent函数 function createComponent (Ctor, data, context, children, 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最后都会通过patch函数进行渲染,将vnode转换成真实的dom,对于组件则会通过createComponent进行渲染

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 */); } // after calling the init hook, if the vnode is a child component // it should've created a child instance and mounted it. the child // component also has set the placeholder vnode's elm. // in that case we can just return the element and be done. if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm); if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); } return true } } }

接下去分两步介绍

keep-alive组件本身的渲染

keep-alive包裹组件的渲染,本例中的Foo组件和Bar组件

先讲讲本例中针对keep-alive组件本身的渲染

根组件实例化

根组件$mount

根组件调用mountComponent

根组件生成renderWatcher

根组件调用updateComponent

根组件调用vm.render()生成根组件vnode

根组件调用vm.update(vnode)

根组件调用vm.patch(oldVnode, vnode)

根组件调用createElm(vnode)

在children渲染的时候,如果遇到组件类型的vnode则调用createComponent(vnode),而正是在这个过程中,进行了子组件的实例化及挂载($mount)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/726f2acbfe7dcb4fd429c505f8ed4cad.html