收錄待用,修改轉載已取得騰訊雲授權
導語
現今的前端開發都講究模塊化組件化,即把公共的交互和功能封裝到一個個的組件之中,在開發整體界面的時候就能像搭積木一樣快速清晰高效。在使用Vue開發我們的vhtml-ui的組件庫的過程中遇到了組件嵌套組件時需要傳遞scopedSlot的情況,官方的文檔和教程目前還沒有比較明確的指引,所以摸着石頭過河,調通了想要的效果。記錄下來方便大家和自己。
在Vue中,為了讓組件可以組合,我們使用Slot來混合父組件的內容與子組件自己的模板。這樣就實現了Vue的內容分發。
Scoped Slot(作用域插槽)是在Vue 2.1引入的更進階的功能,它是一種特殊類型的slot,用作使用一個(能夠傳遞數據到)可重用模板替換已渲染元素。我的理解就是使用scoped slot能在插槽里自定義模板並且使用組件傳遞過來的context。這大大提高了組件開發的靈活性。
Select組件一期
在開發我們的select組件時很自然就用上了scoped slot這一特性。我們需要遍歷數據中的選項數組,渲染成界面上的下拉選項列表。如果是比較復雜的允許自定義的list item,在組件里寫死dom結構就行不通了,比如:
有了scoped slot實現很輕松:
<v-select kind="popup" :options="options4">
<template slot="listItem" scope="props">
<div>
<div><span>{{ props.item.text }}<span> | <span>{{ props.item.value }}<span></div><div>{{ props.item.area }}</div>
<div>{{ props.item.url }}</div>
</div>
</template>
</v-select>
很好,非常好,現在有一個新需求:這個列表有的時候想要脫離select使用,比如就直接展示在頁面上,不需要通過下拉彈出。
select-list組件
這好辦啊,作為組件開發的老司機們自然能想到把這個list獨立做成一個組件,頁面可以直接調用,select組件也可以在它之上再封裝一層。
完美!
開干!
select-list template結構示意:
<ul class="v-select-list">
<li class="v-select-list__item" v-for="(item, index) in options">
<slot name="listItem" :item="item">
<span>{{ item.text }}</span>
</slot>
</li>
</ul>
select template結構示意:
<div class="v-select">
<v-popper>
<v-select-list>
<slot name="listItem">
</slot>
</v-select-list>
</v-popper>
</div>
然后問題來了,最里層的select-list組件並沒有接收到用戶自定義的scoped slot。通過查找Vue官方文檔以及谷歌,也沒有找到使用template方式傳遞scoped slot的介紹和例子。
Render函數和JSX
人總不能讓尿給憋死,一條路走不通我們就看看有沒有其他辦法。在Vue的官方文檔上有這么一句話:
“ Vue 推薦在絕大多數情況下使用 template 來創建你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全編程的能力,這就是 render 函數,它比 template 更接近編譯器。”
查看文檔,通過render函數確實能夠傳遞scoped slot,以下圖的方式
把scoped slot作為createElement方法的第二參數(data object)的一個屬性傳遞到子組件中。
但是render函數的缺點就是不靈活,特別是在定義你的組件的dom結構模板的時候,如果寫很多 render 函數,可能會覺得痛苦。它比較適用於外層組件僅僅是對內層組件的一次邏輯封裝,而渲染的模板結構變化擴展不多的情況。
還好我們還有最后一把殺手鐧--JSX。它可以讓我們回到於更接近模板的語法上。具體關於JSX的使用不是本文的主題,我們可以閱讀使用文檔 ,學習關於 JSX 映射到 JavaScript的用法。
JSX實現上文的嵌套例子
通過參閱文檔及不斷地摸索,最終實現了自己想要的功能。我們直接上關鍵代碼(select的render函數),看看有什么奧秘:
render(h) {
let directives = [{
name: 'popper',
arg: 'selector',
modifiers: { click: true }
}];
return (
<div
class={{ 'v-select': true, 'is-open': this.isPopperShown, 'is-disabled': this.disabled }}>
<v-popper
ref="selector"
placement="bottom-start"
onVisibleChange={this.togglePopper}>
<v-select-list
class="v-select__options"
onChange={this.handleChange}
multiple={this.multiple}
value={this.currentValue}
onInput={this.handleListInput}
scopedSlots={{listItem: this.$scopedSlots.listItem}} >
</v-select-list>
</v-popper>
<div
class="v-select__header"
{ ...{directives} }>
{
this.currentIndex !== -1 && this.$scopedSlots.headItem ? this.$scopedSlots.headItem(this.current)
: (<span>{this.currentText}</span>)
}
<v-icon
class="v-select__header-arrow"
name="v-arrow_dropdown">
</v-icon>
</div>
</div>
)
}
關鍵點:
-
在子組件的標簽上通過
scopedSlots
屬性可以向其傳遞自己的scoped slot; -
自身的scoped slot可以通過
this.$scopedSlots
對象獲取,默認就是default,具名slot就是它的名字。本例為“listItem”; -
如果不在標簽上傳遞而是需要使用表達式傳遞,也可以通過
this.$scopedSlots
對象。並且一個具體的scoped slot對象其實就是一個函數,其內部的scope可以在參數中傳入。比如本例中的this.$scopedSlots.headItem(this.current)
JSX中對template常用點的轉換
上面的介紹涵蓋了基本的用法,但是我們在組件中往往會用一些不基本但常用的vue特性。我們接下來一起看看。
細心地小伙伴可能發現了上面的代碼中已經出現了這些用法
directives
如果我們在組件中使用了directives,那么jsx里就不能想之前在template里那么自然的書寫
v-popper:third.click
而是需要workaround:
-
Pass everything as an object via value, e.g. v-name={{ value, modifier: true }}
-
Use the raw vnode directive data format:
上面的例子中就是用了方法二。
v-model
render函數(JSX也是寫在render函數中)中沒有與v-model
相應的api -
你必須自己來實現相應的邏輯。即通過value屬性傳遞值,並通過綁定input事件來響應變化。
沒有template 中的v-if
和 v-for
:
這意味着我們需要在render函數或者JSX的表達式中手寫if-else邏輯判斷。或者如本例中使用三目表達式來實現。
這就是深入底層要付出的,盡管麻煩了一些,但你可以更靈活地控制。
希望這邊文章能讓我們在開發Vue組件的時候少走一些彎路,如果有大神有更好的辦法或直接在template中實現傳遞scoped slot的功能,請多多指教!