"Vue source code learning" do you really know how Slot slot is "inserted"

Posted by d-m on Thu, 23 Dec 2021 18:15:37 +0100

Hello, I'm Lin Sanxin. Vue has implemented a set of content distribution API s, using the < slot > element as an exit to host the distribution content. This is the description on the Vue document. Specifically, a slot is a 'space' that allows you to add content to a component. Do you really know how a slot is "inserted"? I hope you can read this article as simply and honestly as I do.

Basic use of Vue slot

Single slot | anonymous slot

//Sub component: (assumed name: child)
<template>
  <div class= 'child'>
      
  </div>
</template>

//Parent component: (reference child)
<template>
  <div class= 'app'>
     <child> 
        Lin Sanxin
     </child>
  </div>
</template>

We know that if the content "Lin Sanxin" is added directly to < child > < / child > in the parent component, the text of "Lin Sanxin" will not be rendered on the page. So how can we make the added content display? Just add a slot in the sub component.

//Sub component: (assumed name: child)
<template>
  <div class= 'child'>
      <slot></slot>
  </div>
</template>

Compilation scope (the parent component inserts data at the child component < slot > < / slot >)

As we have seen above, a slot is actually a 'space' that allows us to add content to the child component in the parent component. We can add any data value in the parent component, such as:

//Parent component: (reference child)
<template>
  <div class= 'app'>
     <child> {{ parent }}</child>
  </div>
</template>

new Vue({
  el:'.app',
  data:{
    parent:'Parent component'
  }
})

The syntax for using data has not changed at all, but can we directly use the data in the subcomponent? Obviously not!!

// Sub component: (assumed name: child)
<template>
  <div class= 'child'>
       <slot></slot>
  </div>
</template>

new Vue({
  el:'child',
  data:{
    child:'Subcomponents'
  }
})

// Parent component: (reference child)

<template>
  <div class= 'app'>
     <child> {{ child }}</child>
  </div>
</template>

It is not allowed to directly transfer data into sub components. Because: all contents in the parent template are compiled in the parent scope; Everything in the sub template is compiled in the sub scope.

Backup content (sub component < slot > < / slot > set default value)

The so-called back content is actually the default value of slot. Sometimes if I don't add content to the parent component, slot will display the default value, such as:

//Sub component: (assumed name: child)
<template>
  <div class='child'>
      <slot>This is the default</slot>
  </div>
</template>

Named slot (insert content corresponding to multiple < slot > < / slot > sub components)

Sometimes, there may be more than one slot in the child component, so how can we insert the corresponding content in the parent component exactly at the desired position? Just give the slot a name, that is, add the name attribute.

//Sub component: (assumed name: child)
<template>
  <div class= 'child'>
      <slot name='one'> This is the default value of 1</slot>
      <slot name='two'> This is the default value of 2 </slot>
      <slot name='three'> This is the default value of 3 </slot>
  </div>
</template>

The parent component adds content by, or slot="name" (old syntax), v-slot:name or #name (new syntax):

//Parent component: (reference child)
<template>
  <div class= 'app'>
     <child> 
        <template v-slot:"one"> This is inserted into one Contents of slot </template>
        <template v-slot:"two"> This is inserted into two Contents of slot </template>
        <template v-slot:"three"> This is inserted into three Contents of slot </template>
     </child>
  </div>
</template>

Scope slot (parent component uses child component data at child component < slot > < / slot >)

Through slot, we can add content for child components in the parent component. By naming the slot, we can add content in more than one location. However, the data we add is in the parent component. As mentioned above, we can't directly use the data in sub components, but do we have other methods to use the data of sub components? In fact, we can also use the slot scope method:

//Sub component: (assumed name: child)
<template>
  <div class= 'child'>
      <slot name= 'one' :value1='child1'> This is the default value of 1</slot>    //Bind the data of child1
      <slot :value2='child2'> This is the default value of 2 </slot>  //Bind the data of child2. I don't name slot here
  </div>           
</template>

new Vue({
  el:'child',
  data:{
    child1:'Data 1',
    child2:'Data 2'
  }
})

//Parent component: (reference child)
<template>
  <div class='app'>
     <child> 
        <template v-slot:one='slotone'>  
           {{ slotone.value1 }}    // Assign the value1 value of the sub component to slot one through the syntax of v-slot 
        </template>
        <template v-slot:default='slotde'> 
           {{ slotde.value2 }}  // As above, since the sub component does not name the slot, the default value is default
        </template>
     </child>
  </div>
</template>

How is the Slot "inserted" (popular version)

Ordinary slot

//Sub component: (assumed name: child)
<template>
  <div class='child'>
      I'm in the sub assembly
      <slot></slot>
      <slot name="one"></slot>
  </div>
</template>

//Parent component: (reference child)
<template>
  <div class= 'app'>
     <child> 
        This is what is inserted into the default slot {{parent}}
        <template v-slot:"one"> This is inserted into one Contents of slot {{parent}}</template>
     </child>
  </div>
</template>

new Vue({
  el:'.app',
  data:{
    parent:'Value of parent component'
  }
})
  1. The parent component resolves first, treats the child as a child element, treats the slot as a child element of the child, and obtains the value of the parent variable within the scope of the parent component to generate such a node:

    {    
      tag: "div",    
    children: [{        
       tag: "child",        
       children: ['This is the value of the parent component of the content inserted into the default slot', 
                   'This is inserted into one The value of the content parent component of the slot']
      }]
    }
  2. As a placeholder, slot will be parsed into a function, which roughly means as follows

    {    
     tag: "div",    
     children: [
         'I'm in the sub assembly',
         _t('default'), // Anonymous slot, default name is default
         _t('one') // Named slot named one
     ]
    }
  3. _ The t function needs to pass in the slot name, default by default, and name for the named slot. The function is to get the slot node parsed in the first step, and then return the parsed node. Then the node of the sub component is complete, and the slot has successfully recognized the parent - div tag

    {    
     tag: "div",    
     children: ['I'm in the sub assembly', 
                 'This is the value of the parent component of the content inserted into the default slot', 
                 'This is inserted into one The value of the content parent component of the slot']
    }

Scope slot

//Sub component: (assumed name: child)
<template>
  <div class= 'child'>
      <slot :value1='child1' :value2='child1'></slot>
      <slot name='one' :value1='child2' :value2='child2'></slot>
  </div>           
</template>

new Vue({
  el:'child',
  data:{
    child1: 'Sub data 1',
    child2: 'Sub data 2'
  }
})

//Parent component: (reference child)
<template>
  <div class='app'>
     <child> 
         <template v-slot:default='slotde'> 
            Insert default slot in{{ slotde.value1 }}{{ slotde.value2 }}
        </template>
        <template v-slot:one='slotone'> 
            insert one slot in{{ slotone.value1 }}{{ slotone.value2 }}
        </template>
     </child>
  </div>
</template>
  1. The process is very complicated. Let's talk about it in a popular way. The parent component parses it first. If it encounters a scope slot, it will encapsulate the slot into a function and save it to the child element child

    {    
     tag: "div",    
      children: [{        
      tag: "child"
      scopeSlots:{            
          default (data) { // Remember this data parameter               
              return ['insert one slot Insert default in slot in' + data.value1 + data.value2]
          },
          one (data) { // Remember this data parameter             
              return ['insert one slot in' + data.value1 + data.value2]
          }
      }
     }]
    }

2. It's the sub component's turn to parse. At this time_ The t function appears again, and the sub component wraps the corresponding slot data into an object and passes it in_ T function

{    
 tag: "div",    
   children: [
     'I'm in the sub assembly',
      _t('default',{value1: 'Sub data 1', value2: 'Sub data 1'}),
      _t('one',{value1: 'Sub data 2', value2: 'Sub data 2'})
      
    ]
  }

Next is_ t is executed internally. The wrapped object is passed into the corresponding functions in scopeSlots as a data parameter, which is parsed into:

{    
  tag: "div",    
   children: [
      'I'm in the sub assembly', 
      'Insert default slot Neutron data 1',
      'insert one slot Neutron data 2'
   ]
}

$slots

After seeing this, I'm sure you have understood the process (although it's not very detailed). Then there's another question. Where do these resolved node VNode objects exist? You can't resolve them and throw them away. You must find a place to store them, and then render the real dom. This place is $slots

//Sub component: (assumed name: child)
<template>
  <div class= 'child'>
      <slot></slot>
      <slot name='one'></slot>
      <slot name='two'></slot>
      <slot name='three'></slot>
  </div>
</template>

new Vue({
  el:'.child',
  created () {
      console.log(this.$slots) // Look what's inside
  }
})
//Parent component: (reference child)
<template>
  <div class= 'app'>
     <child> 
        <template> This is what is inserted into the default slot </template>
        <template v-slot:"one"> This is inserted into one Contents of slot </template>
        <template v-slot:"two"> This is inserted into two Contents of slot </template>
        <template v-slot:"three"> This is inserted into three Contents of slot </template>
     </child>
  </div>
</template>

console.log results:

We all know that $slots is a Map, the key is the name of each slot (the key of the anonymous slot is default), and the value corresponding to the key is the VNode node under each slot. You can output what the specific VNode object looks like. There are too many things in it, so I won't show it here. Hey hey.

epilogue

I'm Lin Sanxin, an enthusiastic front-end rookie programmer. If you are self-motivated, like the front end and want to learn from the front end, we can make friends and fish together. Ha ha, fish school, add me, please note [Si no]

Topics: Front-end ECMAScript Vue.js Interview source code