Vue組件開發實踐之scopedSlot的傳遞


收錄待用,修改轉載已取得騰訊雲授權


導語

現今的前端開發都講究模塊化組件化,即把公共的交互和功能封裝到一個個的組件之中,在開發整體界面的時候就能像搭積木一樣快速清晰高效。在使用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>&nbsp;|&nbsp;<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:

  1. Pass everything as an object via value, e.g. v-name={{ value, modifier: true }}

  2. Use the raw vnode directive data format:

上面的例子中就是用了方法二。

v-model

render函數(JSX也是寫在render函數中)中沒有與v-model相應的api - 你必須自己來實現相應的邏輯。即通過value屬性傳遞值,並通過綁定input事件來響應變化。

沒有template 中的v-ifv-for:

這意味着我們需要在render函數或者JSX的表達式中手寫if-else邏輯判斷。或者如本例中使用三目表達式來實現。

這就是深入底層要付出的,盡管麻煩了一些,但你可以更靈活地控制。

希望這邊文章能讓我們在開發Vue組件的時候少走一些彎路,如果有大神有更好的辦法或直接在template中實現傳遞scoped slot的功能,請多多指教!


原文鏈接:https://www.qcloud.com/community/article/693017


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM