Another important concept of Vue components is slots, which allow you to combine components in a way that is different from strict parent-child relationships. Slots give you an outlet to place content in a new location or make components more generic. This section will focus on the introduction of slots on the official website. In accordance with the idea of ordinary slots, named slots, and then scope slots, the internal implementation principle is gradually deepened. If you are not familiar with the use of slots, you can first refer to the introduction of slots on the official website.

10.1 Common Slot

Slot The
is used as a distribution carrier for sub-components. The simple usage is as follows

10.1.1 Basic Usage
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> '}"child">test</div>
Copy the code
10.1.2 Component Mounting Principles

The principle of slots runs through the whole process of compiling a component system to render, so it is necessary to review the process of compiling and rendering a component first, and briefly summarize the following points:

  1. Mount the instance from the root instance, if handwrittenrenderFunction, directly into$mountMounting process.
  2. onlytemplateThe template needs to be parsed, which is divided into two stages. One is to parse the template intoASTTree, another is to generate execution code based on different platforms, for examplerenderFunction.
  3. $mountThe process is also divided into two steps, the first step is torenderFunction to generateVnodeTree, the child component will bevue-componet-fortagThe other step is to putVnodeRender as a real DOM node.
  4. In the process of creating a real node, if a child placeholder component is encountered, the child component instantiates, which goes back to the first step in the process.

Next, we will focus on the slot analysis of these four specific processes, the detailed analysis of the component process, you can refer to the in-depth analysis of Vue source code – component basis section.

10.1.3 Parent Component Processing

Back to the component instance process, the parent component is mounted before the child component, and there is no special difference between the template parsing and the render generation stage, so we will not analyze it here. Next comes the Vnode generation process of the Render function, where the child placeholder node (that is, child) is encountered, so the child Vnode is created for the child component. CreateComponent performs the process of creating a child placeholder Vnode. We focus on the final Vnode code generation.

// Create the child Vnode procedurefunctionCreateComponent (Ctor, // subclass constructor data, context, // vm instance children, // parent component needs to be distributed tag // child component placeholder){··· / create child vNode, Var Vnode = new 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 constructor var Vnode =functionVNode (tag, the data, the children, text, elm, context, componentOptions, asyncFactory) {... this.com ponentOptions = componentOptions;  // Subcomponent options related}Copy the code

The fourth parameter that the createComponent function receives, children, is what the parent needs to distribute. During the creation of child VNodes, the configuration componentOptions is passed into the Vnode constructor. The final content to be distributed by the parent component in Vnode is in the form of componentOptions, which is the first step in slot analysis.

10.1.4 Subcomponent Process

The last phase of the parent component is to render the Vnode as a real DOM node. In this process, if a child Vnode is encountered, the child component is preferentially instantiated and a series of child component rendering processes are carried out. The child component initializes by calling the init method and, unlike the parent, calls the initInternalComponent method to take the configuration information that the parent has and assign its own configuration options to the child component.

// Initialize the child component 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 as child vnode records information about the var vnodeComponentOptions = parentVnode.com ponentOptions; opts.propsData = vnodeComponentOptions.propsData; opts._parentListeners = vnodeComponentOptions.listeners; / / parent components need to distribute the content of the assignment to grant options to configure _renderChildren opts. _renderChildren = vnodeComponentOptions. Children; opts._componentTag = vnodeComponentOptions.tag;if(options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; }}Copy the code

Finally, the distribution saved by the parent component is obtained in the configuration of the child component instance and recorded in the component instance $options._renderChildren, which is the focus of the second step.

Next comes the initRender phase, in which the _renderChildren attribute is normalized and assigned to the $slot attribute on the child instance. This is the focus of the third step.

functionInitRender (vm) {... the vm.$slots= resolveSlots(options._renderChildren, renderContext); //$slots_renderChildren with the child placeholder (that is, the content to be distributed) is left as a property of the child instance}functionResolveSlots (children,context) {// Children is the Vnode that the parent component needs to distribute to the children. If it does not exist, no content is distributedif(! children || ! children.length) {return {}
    }
    var slots = {};
    for (var i = 0, l = children.length; i < l; i++) {
      var child = children[i];
      var data = child.data;
      // remove slot attribute if the node is resolved as a Vue slot node
      if (data && data.attrs && data.attrs.slot) {
        delete data.attrs.slot;
      }
      // named slots should only be respected if the vnode was rendered inThe // same context. // Branch 1 for the named slot logic, put after analysisif((child.context === context || child.fnContext === context) && data && data.slot ! = null ) { var name = data.slot; var slot = (slots[name] || (slots[name] = []));if (child.tag === 'template') {
          slot.push.apply(slot, child.children || []);
        } else{ slot.push(child); }}else{/ / common slot, the focus of the core logic is constructed {default: [children]} object returns (slots. The default | | (slots. Default = [])), push (child); }}return slots
  }
Copy the code

Which core in ordinary slot processing logic (slots. The default | | (slots. Default = [])). Push (child); That is, it is assigned to the default attribute as an array and stored in the instance of the child component as a $slot attribute.


Then the child component will go through the mounting process, which will also go through the template to the render function, to the Vnode, and finally render the real DOM. In the AST phase, the processing of slot labels is the same as that of other ordinary labels. The difference is that in the AST phase of generating render function, the processing of slot labels is wrapped with _t function. This is the fourth key step

The general flow of subcomponent rendering is summarized as follows

Var code = generate(ast, options); / / the generate implementationfunction generate(ast, options) {
  var state = new CodegenState(options);
  var code = ast ? genElement(ast, state) : '_c("div")';
  return {
    render: ("with(this){return " + code + "}"), staticRenderFns: state.staticrenderfns}} // genElement implementationfunctionGenElement (el, state) {// Processing for slot labels takes the genSlot branchif (el.tag === 'slot') {
    returnGenSlot (el, state)}} // Core genSlot principlefunctionGenSlot (el, state) {/ / slotName the only sign of recording the slot, the default for the default var slotName = el. SlotName | |'"default"'; Var children = genChildren(el, state); var children = genChildren(el, state); // Wrap var res = with _t"_t(" + slotName + (children ? ("," + children) : ' '); // Other processing of named slot......return res + ') '
  }
Copy the code

“With (this){return _c(‘div’,{staticClass:”child”},[_t(“default”)],2)}”

The fifth step is to render the child components as vNodes. The render phase executes the _t() function, which is short for the renderSlot function, and it replaces the distribution content in the Vnode tree. See the implementation logic.

// target._t = renderSlot; // Render function render Vnode function vue.prototype._render =function() {
  var _parentVnode = ref._parentVnode;
  if(_parentVnode) {// formalize slots and assign to$scopedSlotsProperties. vm.$scopedSlots = normalizeScopedSlots(
      _parentVnode.data.scopedSlots,
      vm.$slots, // Record the parent component's slot content VM.$scopedSlots); }}Copy the code

The logic of normalizeScopedSlots is longer, but is not the focus of this section. The $scopedSlots attribute will execute the actual render function, where _t executes as follows:

// Render the slot component contentfunctionRenderSlot (name, fallback, // slot slot backup content (for backup content) props, // child to parent value (scope slot)bindVar scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this$scopedSlots[name]; var nodes; // Named slot branch (ignored for now)if (scopedSlotFn) { // scoped slot
      props = props || {};
      if (bindObject) {
        if(! isObject(bindObject)) {
          warn(
            'slot v-bind without argument expects an Object',
            this
          );
        }
        props = extend(extend({}, bindObject), props); } / / execution when the child component to the value of the parent component into the fn nodes = scopedSlotFn (props) | | fallback. }else{// If the parent placeholder component has no slot content, this.$slotsThere is no value, and the VNode node is the backup content node. nodes = this.$slots[name] || fallback;
    }

    var target = props && props.slot;
    if (target) {
      return this.$createElement('template', { slot: target }, nodes)
    } else {
      return nodes
    }
  }
Copy the code

The renderSlot execution takes what the parent component needs to distribute, and eventually the Vnode tree replaces the parent element’s slot component with the child’s slot component.

The final step is to render the real nodes of the child component. This is nothing special and is consistent with the previous process.

So far, a complete and simple slot flow analysis is completed. Let’s look at the deep usage of slots.

10.2 Slot with backup content

Sometimes it’s useful to have a specific backup (that is, the default) content for a slot, which will only be rendered when no content is provided. The logic of looking at the source code to find a backup content slot is also easy to understand.

var child = {
  template: `<div class="child"> < slot > backup content < / slot > < / div > `} var = new vm Vue ({el:'#app',
  components: {
    child
  },
  template: `<div id="app"><child></child></div> '}) // The parent has no slot content, the child slot will render the backup content <div class="child"> < div style = "text-align: center;Copy the code

The parent component has no content to distribute, and the child component displays the contents of the slot by default. The differences in the source code are reflected in the following points.

  1. The parent component rendering process no longer needs to be owned because it has no child nodes to distributecomponentOptions.childrenProperty to record the content.
  2. So the child component is not available$slotProperty.
  3. The child componentrenderThe function ends up at_tThe function argument takes a second argument, which is passed as an arrayslotThe backup content of the slot. casewith(this){return _c('div',{staticClass:"child"},[_t("default",[_v("test")])],2)}
  4. Render the sonVnodeWill performrenderSlot(_t)Function, the second argumentfallbackHave a value, andthis.$slotsNo value,vnodeWill directly return the backup content as the render object.
functionRenderSlot (name, fallback, // slot slot backup content (for backup content) props, // child to parent value (scope slot)bindObject
){
    if() {...}else{//fallback is the fallback content // If the parent placeholder component has no slot content, this.$slotsThere is no value, and the VNode node is the backup content node. nodes = this.$slots[name] || fallback; }}Copy the code

Finally, the slot’s backup content is rendered when the parent component does not provide the content.


Everything in the parent template is compiled at the parent scope; Everything in a subtemplate is compiled in the subscope.

The content of the parent component template is determined during the compilation of the parent component and stored in the componentOptions property, while the child component has its own init process, which also compiles the template of the child scope, so the two parts are relatively independent.

10.3 Named Slot

Often we need to be flexible in the development of common components using slots, requiring that each template of the parent component corresponds to each slot in the child component. In this case, we can use the name attribute of

, again for a simple example.

var child = {
  template: `<div class="child"><slot name="header"></slot><slot name="footer"></slot></div>`,
}
var vm = new Vue({
  el: '#app',
  components: {
    child
  },
  template: `<div id="app"><child><template v-slot:header><span> Header </span></template><template At the bottom of the v - slot: footer > < span > < / span > < / template > < / child > < / div > `,})Copy the code

Render results:

<div class="child"<span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;"Copy the code

Next, let’s look at how the source code differs in the named slot implementation from the normal slot.

10.3.1 Differences in template compilation

In the AST compilation phase, the parent component is different from the normal node process. The named slot is usually marked with v-slot: in the Template template. This phase is specially processed during the compilation phase. The final AST tree carries scopedSlots to record the contents of the named slot

{scopedSlots: {footer: {...}, the header: {...}}}Copy the code

Parse, generate, parse, generate, parse, generate, parse, generate, parse, generate, parse, generate, parse, generate, parse, generate, parse, generate, parse, generate

with(this){return _c('div',{attrs:{"id":"app"}},[_c('child',{scopedSlots:_u([{key:"header",fn:function() {return [_c('span',[_v("Head")])]},proxy:true},{key:"footer",fn:function() {return [_c('span',[_v("The bottom")])]},proxy:true}}})]]), 1)Copy the code

Obviously, the slot contents of the parent component are encapsulated as an array using the _u function and assigned to the scopedSlots attribute. Each slot is described as an object, with key representing the slot name and fn being a function that returns the result of the execution.

10.3.2 VNode generation phase of the Parent Component

As usual, we enter the Vnode generation phase of the parent component, where the prototype of the _u function is resolveScopedSlots, where the first parameter is the slot array.

_u (target._u = resolveScopedSlots)function resolveScopedSlots (fns,res,hasDynamicKeys,contentHashKey) {
    res = res || { $stable: !hasDynamicKeys };
    for(var i = 0; i < fns.length; i++) { var slot = fns[i]; // fn is an array that needs to be processed recursively.if (Array.isArray(slot)) {
        resolveScopedSlots(slot, res, hasDynamicKeys);
      } else if (slot) {
        // marker for reverse proxying v-slot without scope on this.$slots
        if(slot.proxy) {// Processing for proxy slot.fn. Proxy =true; } // Finally return an object with slotname as property and fn as value res[slot.key] = slot.fn; }}if (contentHashKey) {
      (res).$key = contentHashKey;
    }
    return res
  }
Copy the code

Finally, the parent component’s vnode node has an array of scopedSlots on its data property. To review, there are significant differences in the implementation of named slots, which are retained in the parent component as componentOptions. Child, while named slots are stored in the data property as scopedSlots property.

// vnode
{
  scopedSlots: [{
    'header': fn,
    'footer': fn
  }]
}
Copy the code
10.3.3 Subcomponent Rendering Vnode process

The difference between sub-components in the AST tree parsing phase lies in the parsing of the name attribute of the slot label, while in render’s Vnode generation process, the normalization of the slot will be specially processed for the named slots, back to the code of normalizeScopedSlots

vm.$scopedSlots= normalizeScopedSlots (_parentVnode. Data. ScopedSlots, / / as the first parameter to get the parent component slot vm related data.$slots, // Record the parent component's slot content VM.$scopedSlots
);

Copy the code

The final $scopedSlots attribute on the child component instance will carry the parent component’s slot-related content.

// Subcomponent Vnode {$scopedSlots: [{
    'header': f,
    'footer': f
  }]
}
Copy the code
10.3.4 Subcomponents render the real DOM

This.$scopedSlots will record data related to the contents of the parent component’s slot, so it will branch differently from the normal slot. Function (){return [_c(‘span’,[_v(” header “)])]}. Function (){return [_c(‘span’,[_v(” header “)])]}

functionRenderSlot (name, fallback, // slot props, // child passed to parentbindObject
  ){
    var scopedSlotFn = this.$scopedSlots[name]; var nodes; // For a named slot, the characteristics are$scopedSlotsHave a valueif (scopedSlotFn) { // scoped slot
      props = props || {};
      if (bindObject) {
        if(! isObject(bindObject)) {
          warn('slot v-bind without argument expects an Object',this);
        }
        props = extend(extend({}, bindObject), props); } / / execution when the child component to the value of the parent component into the fn nodes = scopedSlotFn (props) | | fallback. }...}Copy the code

So the child component finds the slot content of the corresponding parent component by slotName.

10.4 Scope Slots

Finally, we can use scope slots to give the parent component’s slot contents access to the child component’s data. In this way, the parent component can use v-slot:[name]=[props] to get the value passed by the child component. The feature on the

element of the child component is called slot Props. In addition, vue versions since 2.6 have deprecated slot-scoped in favor of V-slot instead.

var child = {
  template: `<div><slot :user="user"></div>`,
  data() {
    return {
      user: {
        firstname: 'test'
      }
    }
  }
}
var vm = new Vue({
  el: '#app',
  components: {
    child
  },
  template: `<div id="app"><child><template v-slot:default="slotProps">{{slotProps.user.firstname}}</template></child></div>`
})
Copy the code

Scoped slots work in a similar way to named slots, so let’s move on.

10.4.1 Compilation of the parent component

The use of scoped slots and named slots is basically the same in parent components. The difference is that v-slot defines the props of a slot. For reference to the analysis of named slots, fn takes props during the render phase. That is: with(this){return _c(‘div’,{attrs:{“id”:”app”}},[_c(‘child’,{scopedSlots:_u([{key:”default”,fn:function(slotProps){return [_v(_s(slotProps.user.firstname))]}}])})],1)}

10.4.2 Subcomponent Rendering

During the subcomponent compilation phase :user=”user” is parsed as a property, and finally the _t function is passed as an object parameter during the render generation phase. with(this){return _c(‘div’,[_t(“default”,null,{“user”:user})],2)}

The subcomponent renders the Vnode phase, executing the function renderSlot, which we analyzed earlier. The processing of the scope slot is concentrated in the third parameter passed to the function.

// Render slot component vNodefunctionRenderSlot (name, fallback, props, function) {user: user}bindVar scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this var scopedSlotFn = this$scopedSlots[name]; var nodes; // Named slot branchif (scopedSlotFn) { // scoped slot
      props = props || {};
      if (bindObject) {
        if(! isObject(bindObject)) {
          warn(
            'slot v-bind without argument expects an Object', this ); Extend (extend({},bindObject), props); } / / execution when the child component to the value of the parent component into the fn nodes = scopedSlotFn (props) | | fallback. }Copy the code

Finally, the child component’s slot props are passed as arguments to the executing function. Step back to see why named slots are executed as functions rather than directly returning results. After learning about scope slots, we found that this is where the design is clever. The function form makes execution more flexible. A scope slot simply needs to pass in the slot props as a parameter to get the desired result.

10.4.3 thinking

Scope slots at first it’s hard for me to understand the concept, simple look from the definition and the source of the conclusion, the parent component content of the slot can access to the child components of data, it is not clear information communication between the son of the father, in the events section we know that the son of the father communication between components can completely by $emit events, $on forms to complete, So why do we need to add the concept of a slot props? Let’s look at the author’s explanation.

Slot Prop allows us to convert slots into reusable templates that can render different content based on the input prop

Understanding from my own point of view, the scope slot provides a way, when you need to encapsulate a generic, reusable logic module, and this module provides a convenient to external users, allows you to custom when using the component part of the layout, the scope at this time slot is put to use, to the specific ideas, Let’s take a look at some of the tools libraries Vue Virtual Scroller Vue Promised applying this idea.


  • Vue source code – Options merge (top)
  • Deep analysis of Vue source code – options merge (next)
  • Deep analysis of Vue source – data agent, associated child parent components
  • Deep analysis of Vue source code – instance mount, compilation process
  • Deep analysis of Vue source code – complete rendering process
  • Deep analysis of Vue source code – component foundation
  • Deep analysis of Vue source code – components advanced
  • Deep analysis of Vue source code – responsive system building (on)
  • Deep analysis of Vue source code – responsive system construction (in)
  • In-depth analysis of Vue source code – responsive system construction (next)
  • Deep analysis of Vue source code – come, with me to achieve diff algorithm!
  • Deep analysis of Vue source code – uncover Vue event mechanism
  • Deep analysis of Vue source – Vue slot, you want to know all here!
  • Vue source code – do you understand the V-Model syntax sugar?
  • Deep analysis of Vue source code – Vue dynamic component concept, you will mess?
  • Fully understand keep-alive magic in Vue (1)
  • Fully understand the keep-alive magic in Vue (2)