I have always been interested in slot slots in Vue. Here are some simple understandings of slot slots

Here is a brief illustration of how slots works with an example

  1. The template of the dX-Li child component is as follows:
<li class="dx-li"> <slot> Hello nuggets! </slot> </li>Copy the code
  1. The template of the dX-ul parent component is as follows:
<ul>
    <dx-li>
        hello juejin!
    </dx-li>
</ul>
Copy the code
  1. Combined with the above examples and vUE related source code for analysis
    • Dx-ul parent component template compiler, generate component render function:
    module.exports={
        render:function (){
            var _vm=this;
            var _h=_vm.$createElement; var _c=_vm._self._c||_h; // _vm.v is the function that creates the text VNode for createTextVNodereturn _c('ul', 
                    [_c('dx-li', [_vm._v("hello juejin!")])],
                    1)
        },
        staticRenderFns: []
    }
    Copy the code

    Pass the slot content ‘Hello Juejin! ‘is compiled as a child of the DX-Li child component VNode node.

    • Render the dX-Li subcomponent with the render function of the subcomponent:
    module.exports={
        render:function (){
            var _vm=this;
            var _h=_vm.$createElement; var _c=_vm._self._c||_h; _vm._v is the renderSlot functionreturn _c('li', 
                    {staticClass: "dx-li" }, 
                    [_vm._t("default", [_vm._v("You're digging gold!")])], 
                    2
                )
         },
        staticRenderFns: []
    }
    Copy the code

    To initialize the dX-Li child vue instance, the initRender function is called:

    functioninitRender (vm) { ... // The _renderChildren array is stored as'hello juejin! 'VNode node; RenderContext is usually the parent component Vue instance and in this case the DX-UL component instance VM.$slots= resolveSlots(options._renderChildren, renderContext); . }Copy the code

    Where the resolveSlots function is:

    /** * Convert children VNodes to a slots object. */export functionresolveSlots ( children: ? Array<VNode>, context: ? Component): {[key: string]: Array<VNode>} {const slots = {} // Check whether there are childrenif(! children) {returnSlots} // Traverses the child nodes of the parent component nodefor (leti = 0, l = children.length; i < l; I ++) {const child = children[I] // data is VNodeData, Const data = child.data /* Remove the slot attribute * <span slot="abc"></span> * VNode data = {attrs:{slot:"abc"}, slot: "abc"}, so remove the slot */ of attrsif(data && data.attrs && data.attrs.slot) {delete data.attrs.slot} /* Check whether it is a named slot. If it is a named slot, it also requires that the subcomponent/function subcomponent render context is consistent. Main functions: * The name of the slot is not kept when a named slot needs to be passed to a child of a child component. The child component template: * <div> * <div class="default"><slot></slot></div>
        *    <div class="named"><slot name="foo"></slot></div> * </div> * Parent component template: * <child><slot name="foo"></slot></child> * Main component template: * <parent><span slot="foo">foo</span></parent> * Main renders the result: * <div> * <div class="default"><span slot="foo">foo</span></div>
             <div class="named"></div>
        * </div>
        */
        if((child.context === context || child.fnContext === context) && data && data.slot ! = null) {const name = data. The slot const slot = (slots [name] | | (slots [name] = [])) / / here with father adopts slots in the form of the templateif (child.tag === 'template') {
            slot.push.apply(slot, child.children || [])
          } else {
            slot.push(child)
          }
        } else{/ / return default anonymous slot VNode array (slots. The default | | (slots. Default = [])), push (child)}} / / ignore contains only whitespace in the slotfor (const name in slots) {
        if (slots[name].every(isWhitespace)) {
          delete slots[name]
        }
      }
      return slots
    }
    Copy the code

    The render function of the DX-Li component is called when the dX-Li component is mounted, and the renderSlot function is called during this process:

    export functionRenderSlot (name: string, // Name of slot in child component, anonymous default fallback:? Array<VNode>, // The default content of the child component slot is an Array of vnodes. If there is no slot content, display this content. Object, // The child component is passed to the props of the slotbindObject: ? Object // For <slot v-bind="obj"></slot> obj must be an object):? Array<VNode> {const scopedSlotFn = this.$scopedSlots[name]
          let nodes
          if (scopedSlotFn) { // scoped slot
            props = props || {}
            if (bindObject) {
              if(process.env.NODE_ENV ! = ='production' && !isObject(bindObject)) {
                warn(
                  'slot v-bind without argument expects an Object',
                  this
                )
              }
              props = extend(extend({}, bindObject), was introduced into props props)} / / generate the corresponding VNode nodes = scopedSlotFn (props) | | fallback}elseConst slotNodes = this if the parent does not pass the scoped slot.$slots[name]
            // warn duplicate slot usage
            if (slotNodes) {
              if(process.env.NODE_ENV ! = ='production' && slotNodes._rendered) {
                warn(
                  `Duplicate presence of slot "${name}" found inThe same render tree '+' - this will likely cause render errors. ', this)} // Set vnode. _rendered, rendered, Slot slotNodes. _Rendered =true} / / if there is no incoming slot, it is the default content of slot VNode nodes = slotNodes | | fallback} / / if you need to the child components of subcomponents transfer slot / * for chestnut: * Bar component: < div class ="bar"><slot name="foo"/></div> * Foo: <div class="foo"><bar><slot slot="foo"/ > < / bar > < / div > * main components: < div > < foo > hello < / foo > < / div > * final rendering: < div class ="foo"><div class="bar">hello</div></div>
          */
          const target = props && props.slot
          if (target) {
            return this.$createElement('template', { slot: target }, nodes)
          } else {
            return nodes
          }
        }
    Copy the code

Scoped slots to understand

  1. The template of the dX-Li child component is as follows:
<li class="dx-li">	
    <slot str="You're digging gold!">
	    hello juejin!
    </slot>
</li>   
Copy the code
  1. The template of the dX-ul parent component is as follows:
<ul>
    <dx-li>
        <span slot-scope="scope">
            {{scope.str}}
        </span>
    </dx-li>
</ul>
Copy the code
  1. Combine examples and Vue source code with simple scope slots
  • The dX-ul parent component template is compiled to produce the component render function:
module.exports={
    render:function (){
       var _vm=this;
       var _h=_vm.$createElement;
       var _c=_vm._self._c||_h;
          return _c('ul', [_c('dx-li', {// We can compile to generate an array of objects scopedSlots: _vm._u([{key:"default",
              fn: function(scope) {
                return _c('span', 
                    {},
                    [_vm._v(_vm._s(scope.str))]
                )
              }
            }])
          })], 1)
        },
    staticRenderFns: []
 }
Copy the code

Where _vm._u function:

functionResolveScopedSlots (FNS, / / as an array of objects, see above scopedSlots res) {res = res | | {};for (var i = 0; i < fns.length; i++) {
    if(array.isarray (FNS [I])) {// Call resolveScopedSlots(FNS [I], res); }else{ res[fns[i].key] = fns[i].fn; }}return res
}
Copy the code

The subsequent rendering of subcomponents is similar to slots. The principle of Scoped Slots is basically the same as that of slots, except that when the parent component template is compiled, it generates a function that returns VNode. When a child component matches the parent component’s pass-scope slot function, the function is called to generate the corresponding VNode.

conclusion

The slots/ Scoped Slots principle is very simple. We just need to understand that vUE renders the actual DOM elements according to vNodes when rendering components.

Slots is a slot VNode that is compiled from the parent component and placed into the child component rendering VNode tree when rendering the child component.

Scoped Slots compiles the slots content of the parent component into a function. When rendering the child component, pass in the child component props to generate the corresponding VNode. Finally, the subcomponent returns the VNode tree according to the component render function, and the update renders the actual DOM element. Also, you can see that it is possible to pass slots across components, but you must be careful about named slot passing.

That’s a little bit of my understanding of Slots, but there are a lot of other things about Slots. I hope I can help you. Because my level is limited, there are any mistakes and deficiencies, I would like to point out.