「Vue原始碼學習」你真的知道插槽Slot是怎麼“插”的嗎

Sunshine_Lin發表於2021-12-28
大家好我是林三心,Vue 實現了一套內容分發的 API,將<slot>元素作為承載分發內容的出口,這是Vue文件上的說明。具體來說,slot就是可以讓你在元件內新增內容的‘空間’,你真的知道插槽Slot是怎麼“插”的嗎?我希望你們能像我一樣單純,老老實實地看這篇文章。

image.png

Vue插槽slot的基本使用

單個插槽 | 匿名插槽

//子元件 : (假設名為:child)
<template>
  <div class= 'child'>
      
  </div>
</template>

//父元件:(引用子元件 child)
<template>
  <div class= 'app'>
     <child> 
        林三心
     </child>
  </div>
</template>
我們知道,如果直接在父元件中的<child></child> 中新增內容“林三心”,“林三心”文字是不會在頁面上渲染的。那麼我們如何使新增的內容能夠顯示呢?在子元件內新增slot 即可。
//子元件 : (假設名為:child)
<template>
  <div class= 'child'>
      <slot></slot>
  </div>
</template>

編譯作用域 (父元件在子元件<slot></slot>處插入 data)

上面我們瞭解了,slot 其實就是能夠讓我們在父元件中新增內容到子元件的‘空間’。我們可以新增父元件內任意的data值,比如這樣:
//父元件:(引用子元件 child)
<template>
  <div class= 'app'>
     <child> {{ parent }}</child>
  </div>
</template>

new Vue({
  el:'.app',
  data:{
    parent:'父元件'
  }
})
使用資料的語法完全沒有變,但是,我們能否直接使用子元件內的資料呢?顯然不行!!
// 子元件 : (假設名為:child)
<template>
  <div class= 'child'>
       <slot></slot>
  </div>
</template>

new Vue({
  el:'child',
  data:{
    child:'子元件'
  }
})

// 父元件:(引用子元件 child)

<template>
  <div class= 'app'>
     <child> {{ child }}</child>
  </div>
</template>
直接傳入子元件內的資料是不可以的。因為:父級模板裡的所有內容都是在父級作用域中編譯的;子模板裡的所有內容都是在子作用域中編譯的。

後備內容 (子元件<slot></slot>設定預設值)

所謂的後背內容,其實就是slot的預設值,有時我沒有在父元件內新增內容,那麼 slot就會顯示預設值,如:
//子元件 : (假設名為:child)
<template>
  <div class='child'>
      <slot>這就是預設值</slot>
  </div>
</template>

具名插槽 (子元件多個<slot></slot>對應插入內容)

有時候,也許子元件內的slot不止一個,那麼我們如何在父元件中,精確的在想要的位置,插入對應的內容呢? 給插槽命一個名即可,即新增name屬性。
//子元件 : (假設名為:child)
<template>
  <div class= 'child'>
      <slot name='one'> 這就是預設值1</slot>
      <slot name='two'> 這就是預設值2 </slot>
      <slot name='three'> 這就是預設值3 </slot>
  </div>
</template>
父元件通過,或slot="name"(舊語法)v-slot:name#name(新語法) 的方式新增內容:
//父元件:(引用子元件 child)
<template>
  <div class= 'app'>
     <child> 
        <template v-slot:"one"> 這是插入到one插槽的內容 </template>
        <template v-slot:"two"> 這是插入到two插槽的內容 </template>
        <template v-slot:"three"> 這是插入到three插槽的內容 </template>
     </child>
  </div>
</template>

作用域插槽 (父元件在子元件<slot></slot>處使用子元件 data)

通過slot 我們可以在父元件為子元件新增內容,通過給slot命名的方式,我們可以新增不止一個位置的內容。但是我們新增的資料都是父元件內的。上面我們說過不能直接使用子元件內的資料,但是我們是否有其他的方法,讓我們能夠使用子元件的資料呢? 其實我們也可以使用slot-scope的方式:
//子元件 : (假設名為:child)
<template>
  <div class= 'child'>
      <slot name= 'one' :value1='child1'> 這就是預設值1</slot>    //繫結child1的資料
      <slot :value2='child2'> 這就是預設值2 </slot>  //繫結child2的資料,這裡我沒有命名slot
  </div>           
</template>

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

//父元件:(引用子元件 child)
<template>
  <div class='app'>
     <child> 
        <template v-slot:one='slotone'>  
           {{ slotone.value1 }}    // 通過v-slot的語法 將子元件的value1值賦值給slotone 
        </template>
        <template v-slot:default='slotde'> 
           {{ slotde.value2 }}  // 同上,由於子元件沒有給slot命名,預設值就為default
        </template>
     </child>
  </div>
</template>

Slot插槽是怎麼“插”的(通俗版)

普通插槽

//子元件 : (假設名為:child)
<template>
  <div class='child'>
      我在子元件裡面
      <slot></slot>
      <slot name="one"></slot>
  </div>
</template>

//父元件:(引用子元件 child)
<template>
  <div class= 'app'>
     <child> 
        這是插入到預設插槽的內容 {{parent}}
        <template v-slot:"one"> 這是插入到one插槽的內容 {{parent}}</template>
     </child>
  </div>
</template>

new Vue({
  el:'.app',
  data:{
    parent:'父元件的值'
  }
})
  1. 父元件先解析,把 child 當做子元素處理,把 插槽當做 child 的子元素處理,並且在父元件作用域內得出了parent變數的值,生成這樣的節點:

    {    
      tag: "div",    
    children: [{        
       tag: "child",        
       children: ['這是插入到預設插槽的內容 父元件的值', 
                   '這是插入到one插槽的內容 父元件的值']
      }]
    }
  2. 子元件解析,slot 作為一個佔位符,會被解析成一個函式,大概意思像 解析成下面

    {    
     tag: "div",    
     children: [
         '我在子元件裡面',
         _t('default'), // 匿名插槽,預設名稱為default
         _t('one') // 具名插槽,名稱為one
     ]
    }
  3. _t函式需要傳入插槽名稱,預設是default,具名插槽則傳入name,這個函式的作用,是把第一步解析得到的插槽節點拿到,然後返回解析後的節點,那麼子元件的節點就完整了,插槽也成功認了爹——div標籤

    {    
     tag: "div",    
     children: ['我在子元件裡面', 
                 '這是插入到預設插槽的內容 父元件的值', 
                 '這是插入到one插槽的內容 父元件的值']
    }

作用域插槽

//子元件 : (假設名為: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: '子資料1',
    child2: '子資料2'
  }
})

//父元件:(引用子元件 child)
<template>
  <div class='app'>
     <child> 
         <template v-slot:default='slotde'> 
            插入預設 slot 中{{ slotde.value1 }}{{ slotde.value2 }}
        </template>
        <template v-slot:one='slotone'> 
            插入one slot 中{{ slotone.value1 }}{{ slotone.value2 }}
        </template>
     </child>
  </div>
</template>
  1. 過程很複雜,這裡就通俗點講了,父元件先解析,遇到作用域插槽,會將此插槽封裝成一個函式儲存到子元素 child

    {    
     tag: "div",    
      children: [{        
      tag: "child"
      scopeSlots:{            
          default (data) { // 記住這個data引數               
              return ['插入one slot 中插入預設 slot 中' + data.value1 + data.value2]
          },
          one (data) { // 記住這個data引數             
              return ['插入one slot 中' + data.value1 + data.value2]
          }
      }
     }]
    }

2.輪到子元件解析了,這個時候_t函式又登場了,並且子元件將對應的插槽資料包裝成一個物件,傳進_t函式

{    
 tag: "div",    
   children: [
     '我在子元件裡面',
      _t('default',{value1: '子資料1', value2: '子資料1'}),
      _t('one',{value1: '子資料2', value2: '子資料2'})
      
    ]
  }
接下來就是_t內部執行,包裝後的物件被當做data引數傳入了scopeSlots中的對應的各個函式,解析成:
{    
  tag: "div",    
   children: [
      '我在子元件裡面', 
      '插入預設 slot 中 子資料1 子資料1',
      '插入one slot 中 子資料2 子資料2'
   ]
}

$slots

看到這,相信大家已經清楚了大概一個過程(雖然不是很詳細),那麼又有一個疑問了,這些解析後的節點VNode物件,是存在哪兒呢?你總不能解析出來然後就扔了吧?肯定得找個地方存起來然後進行真實dom的渲染,這個地方就是$slots
//子元件 : (假設名為: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) // 看看裡面有啥
  }
})
//父元件:(引用子元件 child)
<template>
  <div class= 'app'>
     <child> 
        <template> 這是插入到預設插槽的內容 </template>
        <template v-slot:"one"> 這是插入到one插槽的內容 </template>
        <template v-slot:"two"> 這是插入到two插槽的內容 </template>
        <template v-slot:"three"> 這是插入到three插槽的內容 </template>
     </child>
  </div>
</template>
console.log的結果:

image.png

看到這裡大家都懂了,$slots是一個Mapkey是各個插槽的名稱(匿名插槽的keydefault),key對應的value都是各個插槽下面的VNode節點,具體VNode物件裡都是長什麼樣,大家可以自己輸出看看,裡面東西太多,我就不在這展示了。嘿嘿。

結語

我是林三心,一個熱心的前端菜鳥程式設計師。如果你上進,喜歡前端,想學習前端,那我們們可以交朋友,一起摸魚哈哈,摸魚群,加我請備註【思否】

image.png

相關文章