赞
踩
Vue组件的另一个重要概念是插槽,它允许你以一种不同于严格的父子关系的方式组合组件。插槽为你提供了一个将内容放置到新位置或使组件更通用的出口。这一节将围绕官网对插槽内容的介绍思路,按照普通插槽,具名插槽,再到作用域插槽的思路,逐步深入内在的实现原理,有对插槽使用不熟悉的,可以先参考官网对插槽的介绍。
插槽将<slot></slot>
作为子组件承载分发的载体,简单的用法如下
var child = { template: `<div class="child"><slot></slot></div>` } var vm = new Vue({ el: '#app', components: { child }, template: `<div id="app"><child>test</child></div>` }) // 最终渲染结果 <div class="child">test</div>
插槽的原理,贯穿了整个组件系统编译到渲染的过程,所以首先需要回顾一下对组件相关编译渲染流程,简单总结一下几点:
render
函数,则直接进入$mount
挂载流程。template
模板则需要对模板进行解析,这里分为两个阶段,一个是将模板解析为AST
树,另一个是根据不同平台生成执行代码,例如render
函数。$mount
流程也分为两步,第一步是将render
函数生成Vnode
树,子组件会以vue-componet-
为tag
标记,另一步是把Vnode
渲染成真正的DOM节点。接下来我们对slot
的分析将围绕这四个具体的流程展开,对组件流程的详细分析,可以参考深入剖析Vue源码 - 组件基础小节。
回到组件实例流程中,父组件会优先于子组件进行实例的挂载,模板的解析和render
函数的生成阶段在处理上没有特殊的差异,这里就不展开分析。接下来是render
函数生成Vnode
的过程,在这个阶段会遇到子的占位符节点(即:child),因此会为子组件创建子的Vnode
。createComponent
执行了创建子占位节点Vnode
的过程。我们把重点放在最终Vnode
代码的生成。
// 创建子Vnode过程 function createComponent ( Ctor, // 子类构造器 data, context, // vm实例 children, // 父组件需要分发的内容 tag // 子组件占位符 ){ ··· // 创建子vnode,其中父保留的children属性会以选项的形式传递给Vnode 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 ); } // Vnode构造器 var VNode = function VNode (tag,data,children,text,elm,context,componentOptions,asyncFactory) { ··· this.componentOptions = componentOptions; // 子组件的选项相关 }
createComponent
函数接收的第四个参数children
就是父组件需要分发的内容。在创建子Vnode
过程中,会以会componentOptions
配置传入Vnode
构造器中。最终Vnode
中父组件需要分发的内容以componentOptions
属性的形式存在,这是插槽分析的第一步。
父组件的最后一个阶段是将Vnode
渲染为真正的DOM节点,在这个过程中如果遇到子Vnode
会优先实例化子组件并进行一系列子组件的渲染流程。子组件初始化会先调用init
方法,并且和父组件不同的是,子组件会调用initInternalComponent
方法拿到父组件拥有的相关配置信息,并赋值给子组件自身的配置选项。
// 子组件的初始化 Vue.prototype._init = function(options) { if (options && options._isComponent) { initInternalComponent(vm, options); } initRender(vm) } function initInternalComponent (vm, options) { var opts = vm.$options = Object.create(vm.constructor.options); var parentVnode = options._parentVnode; opts.parent = options.parent; opts._parentVnode = parentVnode; // componentOptions为子vnode记录的相关信息 var vnodeComponentOptions = parentVnode.componentOptions; opts.propsData = vnodeComponentOptions.propsData; opts._parentListeners = vnodeComponentOptions.listeners; // 父组件需要分发的内容赋值给子选项配置的_renderChildren opts._renderChildren = vnodeComponentOptions.children; opts._componentTag = vnodeComponentOptions.tag; if (options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; } }
最终在子组件实例的配置中拿到了父组件保存的分发内容,记录在组件实例$options._renderChildren
中,这是第二步的重点。
接下来是initRender
阶段,在这个过程会将配置的_renderChildren
属性做规范化处理,并将他赋值给子实例上的$slot
属性,这是第三步的重点。
function initRender(vm) {
···
vm.$slots = resolveSlots(options
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。