vue作用域插槽,你真的懂了嗎?

Tusi發表於2019-04-03

前言

在網上搜了很多關於作用域插槽的解釋,感覺沒有寫得很具體的吧,我認為應該對元件化有很深的理解才會觸及到這個問題吧,這裡也分享下我自己對於slot-scope的一點理解。

  • slot大家看看文件都懂了,無非就是在子元件中挖個坑,坑裡面放什麼東西由父元件決定。
// 子元件
<template>
  <slot>來啊,我這裡挖了個坑</slot>
</template>

// 父元件
<template>
  <child>
   <!-- 傳入子元件的自定義內容,會填入到子元件的slot插槽中 -->
    <span>我在這放個span,樂意的話,放個元件都行</span>
  </child>
</template>
複製程式碼
  1. 給slot傳入普通文字

slot傳入普通文字

  1. 給slot傳入了一個影象處理元件
    slot傳入普通文字
  • 具名插槽也很簡單,比如有多個插槽,我作為父元件,肯定想區別子元件中的幾個插槽,那就要用slot標籤的name屬性來標識了,而父元件要決定在什麼插槽裡面放什麼內容,就要將name的值賦值給slot屬性傳遞給對應的插槽。如果slot沒有name屬性,就是匿名插槽了,而父元件中不指定slot屬性的內容,就會被丟到匿名插槽中。
// 子元件
<template>
    <section>
        <slot name="article-title">這裡放標題</slot>
        <slot>這裡放作者</slot>
        <slot name="article-content">這裡放文章內容</slot>
    </section>
</template>

// 父元件
<template>
    <section>
        <slot-child>
            <h1 slot="article-title">vue作用域插槽,你真的懂了嗎?</h1>
            <p slot="article-content">好像有點懂了</p>
            <div>王五</div>
        </slot-child>
    </section>
</template>
複製程式碼
  • 最難理解的是作用域插槽。看了文件說明的朋友可能還會有點暈,大概是說在作用域插槽內,父元件可以拿到子元件的資料。子元件可以在slot標籤上繫結屬性值,如:
<slot :nickName="'Tusi'"></slot>
複製程式碼

而父元件通過slot-scope繫結的物件下拿到nickName的值。

<template>
    <section>
        <slot-child>
            <template slot-scope="scope">
                <div>{{scope.nickName}}</div>
            </template>
        </slot-child>
    </section>
</template>
複製程式碼

這裡大家應該都有疑問。這有什麼用?我在子元件用$emit向父元件傳遞資料不就行了?

關於作用域插槽的一點理解

我覺得要從元件之間的資料流向來思考作用域插槽的應用場景。

假設第一個場景,需要你寫一個商品卡片元件,並通過迴圈去展示多個卡片,並且要求能響應每個卡片上的圖片或者其他內容的點選事件而跳轉到商品詳情頁,你會怎麼寫?

淘寶商品列表

我會使用如下的處理方式,首先將商品卡片寫成一個元件Commodity.vue,而在CommodityList.vue中用一個v-for來處理商品卡片列表的展示。

<commodity v-for="(item,index) in commodities" @clickCommodity="onCommodityClick"></commodity>
複製程式碼

Commodity元件通過$emit像父元件傳遞clickCommodity事件,並攜帶商品資料,父元件即可在onCommodityClick方法中得到資料,進行業務處理,這樣便完成了一個基本的由子到父的資料傳遞。

如果再往上抽象一下呢?比如我有多個運營欄目,像淘寶首頁有“有好貨”,“愛逛街”這樣兩個欄目,每個欄目下都需要有一個商品卡片列表,那麼商品卡片列表CommodityList.vue就要抽成元件了。而這個包含多個運營欄目的vue元件我假設它叫ColumnList.vue,在其中通過v-for呼叫了CommodityList元件。

淘寶運營欄目列表

注意:業務來了,我希望把點選商品卡片的業務放在ColumnList.vue中處理。你們想象一下要怎麼做?一種土辦法就是商品按鈕點選時,Commodity元件$emit通知CommodityList.vue,而CommodityList接著把事件用$emit往上拋,那麼ColumnList.vue就能處理這個點選事件了。這樣做完全沒有問題,但是顯得子元件很不純粹,跟業務都扯上關係了。

那麼如何優雅地解決這個問題呢?這個時候,作用域插槽真正派上用場了。

通過作用域插槽將本應該由CommodityList處理的商品卡片點選業務onCommodityClick提升到ColumnList處理。

<el-row :gutter="20">
        <el-col :span="12" v-for="(column, index) in columnList" :key="index">
            <el-card class="box-card card-column">
                <div slot="header" class="clearfix">
                    <span>{{column.columnName}}</span>
                </div>
                <commodity-list :commodities="column.commodityList">
                    <template slot-scope="scope">
                    <!-- 這裡只需要給Commodity元件傳入資料,響應Commodity元件的clickCommodity事件即可。
                        事件不必攜帶引數,完全符合父到子的資料流向,而不會發生子元件又給父元件反向發資料的情況 -->
                        <commodity :modityData="scope.row" @clickCommodity="onCommodityClick(scope.row)"></commodity>
                    </template>
                </commodity-list>
            </el-card>
        </el-col>
</el-row>
複製程式碼

而CommodityList元件內部應該是改造成這樣,slot接收來自父元件的商品卡片元件,這裡面不涉及關於商品元件的業務,只關注其他業務和佈局即可。最終就實現了元件和業務的剝離,這也是元件化的精髓所在吧。不知道有沒有幫到您呢?

<el-row :gutter="20">
        <el-col :span="8" v-for="(item, index) in commodities" :key="index" style="margin-top:20px;">
            <slot :row="item"></slot>
        </el-col>
</el-row>
複製程式碼

這是我實現的效果,忽略樣式吧,原理都懂了,做個漂亮的卡片有多難?

淘寶運營欄目列表
淘寶運營欄目列表

總結一下,作用域插槽適合的場景是至少包含三級以上的元件層級,是一種優秀的元件化方案!

相關文章