首頁 > web前端 > Vue.js > 聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

青灯夜游
發布: 2022-05-10 11:55:03
轉載
2982 人瀏覽過

這篇文章跟大家分享一下Vue乾貨,聊聊你可能不知道的Vue.slot原理,希望對大家有幫助!

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

相信不管是日常業務開發,還是封裝基礎元件,插槽slot 都是經常出現在我們的視野的,因為它為我們編程實作提供了許多便捷。可能大家對於slot 的用法已經爛透於心了,不管是具名插槽 ,還是作用域插槽 各種用法等等...那大?們又知不知道slotslot-scope 底層是怎麼實現的呢?

簡單易懂10分鐘就能帶走Vue.slot乾貨原始碼實作分析! ! !跟著筆者一起探究下 Vue(v2.6.14) 中的插槽 slot 是怎麼實現的! !本文主要會分兩塊來解說:

  • 一般插槽(具名插槽、預設插槽)

  • 作用域插槽

這篇文章沒有晦澀的源碼解析,直接用大白話講解,所以不管大家對Vue源碼的熟悉程度,都是能看明白的。透過現場調試,讓你看清 Vueslot 是如何實現的。 let's go go go! (學習影片分享:vue影片教學

一、回顧 slot 用法

先跟大家一起回顧下插槽的大概用法。這裡的slot 用法使用2.6.0 的新標準(本文也會帶一下v2.5 的寫法的跟v2.6 在原始碼實作上有什麼區別!)。

如果想詳細了解用法可以到官網詳細看看Vue 的slot 文件

https://cn.vuejs.org/v2/guide/components-slots.html

1. 預設插槽

<!-- 子组件 -->
<template>
  <div class="wrapper">
    <!-- 默认插槽 -->
    <div class="main">
      <slot></slot>
    </div>
</template>

<!-- 父组件 -->
<my-slot>
  <template>
    <h1>默认插槽</h1>
  </template>
</my-slot>
登入後複製

頁面展示效果如圖:

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

2. 具名插槽

接著上述的案例,新增具名插槽header ,程式碼如下:

<!-- 子组件 -->
<template>
  <div class="wrapper">
    <!-- header 具名插槽 -->
    <header class="header">
      <slot name="header"></slot>
    </header>
    <!-- 默认插槽 -->
    <div class="main">
      <slot></slot>
    </div>
</template>

<!-- 父组件 -->
<my-slot>
  <template v-slot:header>
    <h1>header 具名插槽</h1>
  </template>
  <template>
    <h1>默认插槽</h1>
  </template>
</my-slot>
登入後複製

如上程式碼區塊可以發現:

  • 子元件中的slot標籤 帶上了一個名為name 的屬性,值為header

  • 父元件中的template標籤 帶上了v-slot 的屬性,值為header

頁面展示效果如圖:

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

3. 作用域插槽(slot-scope)

再接著上述案例,加入作用域插槽footer ,程式碼如下

<!-- 子组件 -->
<template>
  <div class="wrapper">
    <!-- header 具名插槽 -->
    <header class="header">
      <slot name="header"></slot>
    </header>
    <!-- 默认插槽 -->
    <div class="main">
      <slot></slot>
    </div>
    <!-- footer 具名 + 作用域插槽 -->
    <footer class="footer">
      <slot name="footer" :footerInfo="footerInfo"></slot>
    </footer>
  </div>
</template>
<script>
export default {
  name: "mySlot",
  data () {
    return {
      footerInfo: {
        text: &#39;这是 子组件 footer插槽 的作用域数据&#39;
      }
    }
  }
}
</script>

<!-- 父组件 -->
<my-slot>
  <template v-slot:header>
    <h1>header 具名插槽</h1>
  </template>
  <template>
    <h1>默认插槽</h1>
  </template>
  <template v-slot:footer="slotProps">
    <h1>footer 具名 + 作用域插槽</h1>
    <p>{{ slotProps.footerInfo.text }}</p>
  </template>
</my-slot>
登入後複製

如上程式碼區塊可以發現:

  • 子元件中的slot標籤 除了有name=footer的屬性,還有一個:footerInfo="footerInfo" 的屬性(作用就是傳遞子元件資料)

  • 父元件中的template標籤 不只v-slot:footer ,而且還有一個賦值運算="slotProps",在模版的雙括號語法中,直接透過slotProps存取到子元件的footerInfo

頁面展示效果如圖:

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

##好了,簡單回顧完用法後,筆者在這裡先提三個問題:

    普通插槽、 作用域插槽的vNode 是在哪個環節產生的,render 父元件時還是子元件時?
  1. 作用域插槽 為什麼能在父元件存取子元件的資料?
  2. 普通插槽 跟 作用域插槽 在實作上有差別嗎?
我們帶著疑問接著往下看!

二、不同

slot的編譯區別

我們根據上述最終的案例程式碼,執行一下打包指令,看看Vue 在編譯模板的時候,是怎麼處理我們的

slot 的!事不宜遲,趕快build 一哈~(偷偷告訴大?,Vue 處理作用域插槽普通插槽 的差異就是從編譯開始的,也就是render函數會有所不同)

這裡筆者順便使用

v2.5 的具名插槽寫法給大?參考一下(對具名插槽header做改寫,使用slot="header" 的寫法),大家可以看下v2.6v2.5 具名插槽的寫法、實現上的差別~反正也不難,也就順便帶出來看看了

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

上图左边是 v2.6 、右边是 v2.5 的,这里,我们集中关注:

  • scopedSlots 属性。使用作用域插槽的 footer 的 render函数 是放在 scopedSlots 里的,并且 函数中 还有接收一个参数

  • my-slotchildren。可以发现,默认插槽的 render函数 一直都是作为当前组件的childre节点,放在 当前 组件render函数 的第三个参数

  • 关注 header 两种具名插槽写法的区别。

    • v2.6 中,我们使用了具名插槽,但是又未使用 作用域插槽的 header 被放在了 scopedSlots但是函数的参数为空,这点跟作用域插槽有区别
    • v2.5 中, 具名插槽header 仅仅作为 my-slot组件 的children节点,并且其render函数的第二个参数中有一个 slot 的属性。

其实根据上述编译后的结果,我们不妨这样猜测

  • 默认插槽直接在父组件的 render 阶段生成 vNode

    • 子组件 render 时,可能通过某种手段取得已经生成好的 插槽vNode 用作自己的 slot 节点。
    • 因为观察上述默认插槽的render函数:e("h1", [t._v("默认插槽")]),直接就是 my-slot 组件的childre节点(位于 my-slot 组件的第三个参数)。
  • 作用域插槽是在子组件 render 阶段生成 vNode

    • 因为我们观察作用域插槽 footer 的编译后结果,其不作为 my-slot 组件的 children,而是被放在了 my-slot 组件的 第二个参数 data 中的一个 scopedSlots属性里。
    • 并且,作用域插槽的 render 函数 外层的 funciton 接收了一个参数。如果在执行子组件 render 的时候调用,那完全可以拿到子组件的数据。

这里放出具体的 作用域插槽 打包后代码,大家一看就很清晰了:

{
  scopedSlots: t._u([
    {
      key: "footer", 
      // 函数接收了一个参数n    
      fn: function (n) {
        return [
          // h1 标签的 render 函数
          e("h1", [t._v("footer 具名 + 作用域插槽")]), 
          // p 标签的 render 函数,这里可以看到编译后是:n.footerInfo.text
          e("p", [t._v(t._s(n.footerInfo.text))])
        ]
      }
    }
  ])
}
登入後複製

三、slot实现原理

1. 断点调试

为了方便大家看调试结果,当前项目的组件结构主要是这样,有三大层:

Vue -> <app></app> -> <my-slot></my-slot>

这里笔者在运行时代码 initRender()renderSlot() 中,打上 debugger ,直接带大火看看执行流程。这里简单介绍下两个方法:

  • initRender:获取 vm.$slot 。组件初始化时执行(比如执行到 my-slot组件 时,可从vm.$slot 获取父组件中的slot vNode,也就是我们的 默认插槽)

  • renderSlot:把组件的 slot 替换成真正的 插槽vNode

接下来直接看实验截图:

1、先是进入initRender()(这里跳过初始化 大VueApp 的过程)。直接到初始化 my-slot组件 过程。【 简单解释:由于 App组件 执行 render 得到 App组件vNode ,在 patch 过程中 遇到 vue-component-my-slot 的 vNode ,又执行 my-slot组件 的 初始化流程。不是很熟悉组件化流程的朋友可以去看看笔者的Vue响应式原理~】

  • 我们不难发现,图中此时正值 my-slot组件init 阶段。

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

  • 再往下执行,我们可以得到 App组件中的 <h1>默认插槽</h1> 的vNode,赋值给 vm.$slot(这里我们记住,默认插槽的 vNode 已经得到)

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

2、再是進入 renderSlot()。接著上面繼續單步執行,會走到 renderSlot 中。這時候,已經進入到 my-slot元件render 階段了。回顧第一步驟中,此時我們手握預設插槽的vNode,並存在vm.$slot.default

##header插槽

    按順序走,先是render 排第一的header 的vNode。如圖所示,會走到斷點處,我們接著單步

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

    直接進入執行我們 header插槽 的render函數執行處。根據偵錯步驟,
  • 我們可以肯定,放置在scopedSlots屬性 中的render函數,是在子元件render 的時候執行

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

    得到header插槽的vNode

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

#預設插槽

    繼續單步走,這次輪到預設插槽了!如圖所示,這裡的
  • key 正是 'default'。可以發現,這裡並沒有像上面 header插槽 一樣,去執行 render,而是直接將我們之前得到的 插槽vNode回來了。

聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

    得到default插槽的vNode

1聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

作用域插槽

    前面都跟header插槽一致,都是會在my-slot 元件中執行插槽的render。我們直接單步到 render 看看有什麼差別。這裡可以得出,
  • function處傳入的參數正是我們子組件my-slotdata 數據,這就是為什麼我們在App元件 能透過作用域插槽 存取到子元件資料的原因了

1聊聊Vue.slot原理,起探究下slot 是怎麼實現的!

  • 最後也是傳回footer插槽的vNode。好了,驗證過程結束~

2. 總結插槽實現原理

#其實上面的流程只是論證過程,大家不可以不必深陷其中。筆者在這裡直接根據實踐過程,給大夥總結出結論!也就是要回到我們一開始的三個問題!

1、普通插槽、 作用域插槽 的 vNode 是在哪個環節產生的,render 父元件時還是子元件時?

  • 預設插槽,不管v2.5v2.6 的寫法,都是在父元件中生成vNodevNode 存在 vm.$slot 中。待子元件render 到插槽時,會直接拿到父元件的vNode

  • ##具名插槽

    兩個版本情況不一。根據編譯結果可知:

    • v2.5

      的寫法,跟預設插槽是一樣的,在父元件產生vNode,子元件直接拿來用

    • v2.6

      中,直接時在子元件中才去執行插槽render ,產生插槽vNode

  • 作用域插槽

    。不管版本,都是在子元件中進行render的

  • 大家不妨這麼理解,模版編譯後,只要是被放在
  • scopeSlots屬性

    中的插槽,都會在子元件執行render 的時候才會去產生vNode。

  • 2、作用域插槽 為什麼能在父元件存取子元件的資料?

作用域插槽
    只有子元件render的時候
  • ,才會執行render產生vNode。並且,作用域插槽的 render 函數能接參數,從而獲得子元件的資料。就是這樣形成了作用域插槽!所以我們能在父元件中,存取到子元件的data資料。
  • 3、普通插槽 跟 作用域插槽 在實作上有差別嗎?

有差別。
    • 普通插槽

      。如果是v2.5 ,具名插槽和預設插槽都只在父元件render 的時候產生vNode,子元件要渲染slot 的時候,直接在父元件實例的$slot 中取得已經是vNode的資料。

    • 普通插槽。如果是 v2.6 ,則具名插槽 雖然是在子元件中執行的 render,但其不接收參數

    • 作用域插槽。 不管 v2.5v2.6,都只在 子元件執行 render,並且能接收參數

好了,最後來個精煉的總結。 作用域插槽一定是延遲執行,且接收參數!普通插槽 可能延遲執行,可能直接執行,但不接收參數!


寫在最後,很多時候我們搬磚,遵照文檔把功能實現確實省力省心~但當你做多了,你就發現當前的東西缺乏挑戰,索然無味。那這個時候,就會有一種衝動,想深入實現原理,看看 slot 到底是怎麼實現的。特別是作用域插槽。用的時候都會想當然的覺得在上層組件透過作用域插槽拿到子組件的數據理所當然應,但是在深入源碼之後,看懂了別人是怎麼做的,會有突然恍然大悟的感覺~

(學習影片分享:web前端開發程式設計基礎影片

以上是聊聊Vue.slot原理,起探究下slot 是怎麼實現的!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板