天天看點

vue slot scope使用_vue 深度長文之slot 篇

vue slot scope使用_vue 深度長文之slot 篇
今天我們将分析我們經常使用的 vue 功能 slot 是如何設計和實作的,本文将圍繞 普通插槽 和 作用域插槽 以及 vue 2.6.x 版本的 v-slot 展開對該話題的讨論。當然還不懂用法的同學建議官網先看看相關 API 先。接下來,我們直接進入正文吧

普通插槽

首先我們看一個我們對于 slot 最常用的例子

vue slot scope使用_vue 深度長文之slot 篇

然後我們直接使用,頁面則正常顯示一下内容

vue slot scope使用_vue 深度長文之slot 篇

然後,這個時候我們使用的時候,對 slot 内容進行覆寫

this is slot custom content.
           

内容則變成下圖所示

vue slot scope使用_vue 深度長文之slot 篇

對于此,大家可能都能清楚的知道會是這種情況。今天我就将帶領大家直接看看 vue 底層對 slot 插槽的具體實作。

vm.$slots

我們開始前,先看看 vue 的 Component 接口上對 $slots 屬性的定義

$slots: { [key: string]: Array }; 
           

多的咱不說,咱直接 console 一下上面例子中的 $slots

vue slot scope使用_vue 深度長文之slot 篇

剩下的篇幅将講解 slot 内容如何進行渲染以及如何轉換成上圖内容

renderSlot

看完了具體執行個體中 slot 渲染後的 vm.$slots 對象,這一小篇我們直接看看 renderSlot 這塊的邏輯,首先我們先看看 renderSlot 函數的幾個參數都有哪些

vue slot scope使用_vue 深度長文之slot 篇

這裡我們先不看 scoped-slot 的邏輯,我們隻看普通 slot 的邏輯。

const slotNodes = this.$slots[name]nodes = slotNodes || fallbackreturn nodes
           

這裡直接先取值 this.$slots[name] ,若存在則直接傳回其對其的 vnode 數組,否則傳回 fallback。看到這,很多人可能不知道 this.$slots 在哪定義的。解釋這個之前我們直接往後看另外一個方法

vue slot scope使用_vue 深度長文之slot 篇

看完 resolveSlots 的參數後我們接着往後過其中具體的邏輯。如果 children 參數不存在,直接傳回一個空對象

const slots = {}if (!children) { return slots}
           

如果存在,則直接對 children 進行周遊操作

vue slot scope使用_vue 深度長文之slot 篇

slots 擷取到值後,則進行一些過濾操作,然後直接傳回有用的 slots

vue slot scope使用_vue 深度長文之slot 篇

我們從上面已經知道了 vue 對 slots 是如何進行指派儲存資料的。而在 src/core/instance/render.js 的 initRender 方法中則是對 vm.$slots 進行了初始化的指派。

vue slot scope使用_vue 深度長文之slot 篇

了解了是 vm.$slots 這塊邏輯後,肯定有人會問:你這不就隻是拿到了一個對象麼,怎麼把其中的内容給搞出來呢?别急,我們接着就來講一下對于 slot 這塊 vue 是如何進行編譯的。這裡咱就把 slot generate 相關邏輯過上一過,話不多說,咱直接上代碼

vue slot scope使用_vue 深度長文之slot 篇

注:上面的 slotName 在 src/compiler/parser/index.js 的 processSlot() 函數中進行了指派,并且 父元件編譯階段用到的 slotTarget 也在這裡進行了處理

vue slot scope使用_vue 深度長文之slot 篇

随即在 genData() 中使用 slotTarget 進行 data 的資料拼接

if (el.slotTarget && !el.slotScope) { data += `slot:${el.slotTarget},`}
           

此時父元件将生成以下代碼

vue slot scope使用_vue 深度長文之slot 篇

然後當 el.tag 為 slot 的情況,則直接執行 genSlot()

else if (el.tag === 'slot') { return genSlot(el, state)}
           

按照我們舉出的例子,則子元件最終會生成以下代碼

vue slot scope使用_vue 深度長文之slot 篇

作用域插槽

上面我們已經了解到 vue 對于普通的 slot 标簽是如何進行處理和轉換的。接下來我們來分析下作用域插槽的實作邏輯。

1、vm.$scopedSlots

了解之前還是老規矩,先看看 vue 的 Component 接口上對 $scopedSlots 屬性的定義

$scopedSlots: { [key: string]: () => VNodeChildren };
           

其中的 VNodeChildren 定義如下:

declare type VNodeChildren = Array<?VNode | string | VNodeChildren> | string;
           

先來個相關的例子

vue slot scope使用_vue 深度長文之slot 篇

然後進行使用

vue slot scope使用_vue 深度長文之slot 篇

效果如下

vue slot scope使用_vue 深度長文之slot 篇

從使用層面我們能看出來,子元件的 slot 标簽上綁定了一個 text 以及 :msg 屬性。然後父元件在使用插槽使用了 slot-scope 屬性去讀取插槽帶的屬性對應的值

注:提及一下 processSlot() 對于 slot-scope 的處理邏輯

vue slot scope使用_vue 深度長文之slot 篇

從上面的代碼我們能看出,vue 對于這塊直接讀取 slot-scope 屬性并指派給 AST 抽象文法樹的 slotScope 屬性上。而擁有 slotScope 屬性的節點,會直接以 **插槽名稱 name 為 key、本身為 value **的對象形式挂載在父節點的 scopedSlots 屬性上

然後在 src/core/instance/render.js 的 renderMixin 方法中對 vm.$scopedSlots 則是進行了如下指派:

if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject}
           

然後 genData() 裡會進行以下邏輯處理

if (el.scopedSlots) { data += `${genScopedSlots(el, el.scopedSlots, state)},`}
           

緊接着我們來看看 genScopedSlots 中的邏輯

vue slot scope使用_vue 深度長文之slot 篇

然後我們再來看看 genScopedSlot 是如何生成 render function 字元串的

vue slot scope使用_vue 深度長文之slot 篇

我們把上面例子的 $scopedSlots 列印一下,結果如下

vue slot scope使用_vue 深度長文之slot 篇

然後上面例子中父元件最終會生成如下代碼

vue slot scope使用_vue 深度長文之slot 篇

renderSlot(slot-scope)

上面我們提及對于插槽 render 邏輯的時候忽略了 slot-scope 的相關邏輯,這裡我們來看看這部分内容

vue slot scope使用_vue 深度長文之slot 篇

這裡我們看看 renderHelps 裡面的 _u ,即 resolveScopedSlots,其邏輯如下

vue slot scope使用_vue 深度長文之slot 篇

這塊會對 attrs 和 v-bind 進行,對于這塊内容上面我已經提過了,要看請往上翻閱。結合我們的例子,子元件則會生成以下代碼

vue slot scope使用_vue 深度長文之slot 篇

v-slot

1、基本用法

vue 2.6.x 已經出來有一段時間了,其中對于插槽這塊則是放棄了 slot-scope 作用域插槽推薦寫法,直接改成了 v-slot 指令形式的推薦寫法(當然這隻是個文法糖而已)。下面我們将仔細談談 v-slot 這塊的内容。

在看具體實作邏輯前,我們先通過一個例子來先了解下其基本用法

vue slot scope使用_vue 深度長文之slot 篇

然後進行使用

vue slot scope使用_vue 深度長文之slot 篇

頁面展示效果如下

vue slot scope使用_vue 深度長文之slot 篇

相同與差別

接下來,咱來會會這個新特性

round 1. $slots & $scopedSlots

$slots 這塊邏輯沒變,還是沿用的以前的代碼

// $slotsconst options = vm.$optionsconst parentVnode = vm.$vnode = options._parentVnodeconst renderContext = parentVnode && parentVnode.contextvm.$slots = resolveSlots(options._renderChildren, renderContext)
           

$scopedSlots 這塊則進行了改造,執行了 normalizeScopedSlots() 并接收其傳回值為 $scopedSlots 的值

vue slot scope使用_vue 深度長文之slot 篇

接着,我們來會一會 normalizeScopedSlots ,首先我們先看看它的幾個參數

vue slot scope使用_vue 深度長文之slot 篇
  • 首先,如果 slots 參數不存在,則直接傳回一個空對象 {}
if (!slots) { res = {}}
           
  • 若 prevSlots 存在,且滿足系列條件的情況,則直接傳回 prevSlots
vue slot scope使用_vue 深度長文之slot 篇

注:這裡的 $key , $hasNormal , $stable 是直接使用 vue 内部對 Object.defineProperty 封裝好的 def() 方法進行指派的

def(res, '$stable', isStable)def(res, '$key', key)def(res, '$hasNormal', hasNormalSlots)複制代碼
           
  • 否則,則對 slots 對象進行周遊,操作 normalSlots ,指派給 key 為 key,value 為 normalizeScopedSlot 傳回的函數 的對象 res
vue slot scope使用_vue 深度長文之slot 篇
  • 随後再次對 normalSlots 進行周遊,若 normalSlots 中的 key 在 res 找不到對應的 key,則直接進行 proxyNormalSlot 代理操作,将 normalSlots 中的 slot 挂載到 res對象上
  • 接着,我們看看 normalizeScopedSlot() 都做了些什麼事情。該方法接收三個參數,第一個參數為 normalSlots,第二個參數為 key,第三個參數為 fn
vue slot scope使用_vue 深度長文之slot 篇

參考文章:

https://juejin.im/post/5cced0096fb9a032426510ad