preface

The introduction of slots allows components to communicate with each other beyond the use of Refs and prop. Slots can pass scoped properties as well as DOM elements, and can also insert specified tag locations, making packaging components more flexible

Use of slots

There are some changes in the slot API, slot-scope has been indicated to be deprecated, so we are talking about the V-slot property here, and we will briefly introduce the simple use of slots

1. Anonymous slot

Anonymous slots have no scope, no alias recognition, and replace all DOM elements wrapped in the children tag of the child component with tags in the child component

/ / parent component < div id = "app" > < children > < p > passed to the content of the slot 1 < / p > < p > passed to the content of the slot 2 < / p > < / children > < / div > / / subcomponents < div > < slot / > < / div >Copy the code

2. Alias slot

The advantage of alias slots is that instead of having to shuttle everything into a subclass, you can specify what you want to insert and where you want to insert it by alias. It allows you to customize content, greatly increasing the flexibility of component encapsulation

// Parent component <div id="app"> <children> <div> Anonymous content 1</div> <div> anonymous content 2</div> <template V-slot :header> <p> Header </p> </template> <template V-slot :footer> <p> tail </p> </template> </children> </div> // subcomponent <div> <slot name="header"/> <slot name="footer"/> <slot/> </div>Copy the code

3. Scope slot

Ordinary slot is only transfer DOM element, but the actual demand tends to be diversified, scope slot is on the basis of the original ordinary slot for data transmission, slot = “will be the parent components written content is inserted into the DOM subcomponents, scope slot =” in child components can be the content of the slot for the parent component for data transmission

// Parent component <div id="app"> <children> <div slot-scope="scope"> <p>{{scope.data}}</p> </div> <template V-slot :header="scope"> < p > {{scope. Data}} < / p > < / template > < / children > < / div > / / subcomponents < div > < p > hello world < / p > < slot data = "slot by value" / > < slot Name ="header" data=" alias slot pass "/> </div>Copy the code

Slot implementation

We know that the HTML written in Vue will eventually be parsed into Vnode, want to know how to realize the slot, we need to know how the component is resolved into Vnode data format, the following start to use graphic way to illustrate the slot is how to achieve

Following the principle of parsing, the DOM elements of the parent and child components above are parsed into vNodes as shown above.

Now it’s a straightforward situation, because the contents of the slot are written in the parent component, but its final rendering position is in the child component, For componentsOptions, replace all vNOdo subclasses of Children with vNode (” Slot “). The scoped and alias components are add-ons to the base component. The data attribute in each VNode is the attributes attribute that stores the current DOM. Alias and scope attribute values are stored in the data attribute to realize value transmission and location identification

Is so simple, that Vue source code is how to replace it?

Each component of the Vue calls its $mount method to parse the TEMPLATE DOM element and generate the real DOM

We could also write a render function to parse the template, as the child component would look like this

/ / subcomponents < div > < p > I am a child component < / p > < slot / > < / div > / / render function $createElement method (' div ', {}, [$createElement method (' p ', {}, [' I am a child components']) _t (' default ')])Copy the code

How is the $createElement function implemented internally? Move on to the last article I wrote

How are Vue components generated?

So what’s going on here with the _t function? Actually it is a tag = ‘childrent’ component componentsOptions inside. The inside of the children all vnode content to the current position

Inside Vue it looks like this

Export function renderSlot (name: string, // slot alias, anonymous slot default assigned name: default fallback:? Array<VNode>, props: ? Object, // Scope slot pass parameter bindObject:? Object): ? Array<VNode> {// Get the contents of the <slot/> slots in the child component that correspond to the slots in the parent component. This represents the current component object. Perform and then returned to the vnode + const scopedSlotFn = this. $scopedSlots [name] let nodes if (scopedSlotFn) {/ / scoped slot props = props | |  {} if (bindObject) { props = extend(extend({}, bindObject), Props)} / / get the need to insert the vnode + nodes = scopedSlotFn (props) | | fallback} else {$slots nodes = this. [name] | | fallback} const target = props && props.slot if (target) { return this.$createElement('template', { slot: target }, nodes) } else { return nodes } }Copy the code

This. ScopedSlots \[name\] gets the contents of the slot from the current component instance this.scopedSlots\[name\] and returns the contents. Step inside the vue.prototype. _render function:

Vue.prototype._render = function (): VNode {    
  const vm: Component = this    
  const { render, _parentVnode } = vm.$options    
  if (_parentVnode) {      
    vm.$scopedSlots = normalizeScopedSlots( 
      _parentVnode.data.scopedSlots,       
      vm.$slots,         
      vm.$scopedSlots      
    )
}
Copy the code

The normalizeScopedSlots function wraps the vNode of slots into a function. The first and third parameters of normalizeScopedSlots do not need to be inside the vNode. It’s a Vnode that stores slots, the content of slots, it’s a Vnode that stores slots, so where is the slots attribute mounted? Open the Vue. Prototype. InitRender

export function initRender (vm: Component) {  
vm._vnode = null 
vm._staticTrees = null 
const options = vm.$options  
......
vm.$slots = resolveSlots(options._renderChildren, renderContext)
Copy the code

Its value is in options._renderChildren, so where is this property mounted?

This. Init (options) is called in the new Vue, and the first thing to do is to call the megeOptions component to merge configuration items. The child component calls the initInternalComponent method to merge configuration items, and the initInternalComponent method points _renderChildren to the vNode corresponding to the children tag parsed in the parent component

{ tag: 'children', data: { }, componentsOptions: { children: [{ tag: 'p', data: {}, children: [{ text: }]} children: []}Copy the code

In this way, the twists and turns of the route can be summed up as: The parent component template calls the render function to parse to vNode, and then calls the update method to generate the real DOM of vnode. Extend to create a child child of Vue. Extend and pass vNode to preserve the parent-child relationship, so that the children component gets all the contents of the vNode passed by the parent component