詳解Vue的slot新用法

摘要: 理解Vue插槽。javascript

Fundebug經受權轉載,版權歸原做者全部。html

爲了保證的可讀性,本文采用意譯而非直譯。前端

最近發佈不久的Vue 2.6,使用插槽的語法變得更加簡潔。 對插槽的這種改變讓我對發現插槽的潛在功能感興趣,以便爲咱們基於Vue的項目提供可重用性,新功能和更清晰的可讀性。 真正有能力的插槽是什麼?vue

若是你是Vue的新手,或者尚未看到2.6版的變化,請繼續閱讀。也許學習插槽的最佳資源是Vue本身的文檔,可是我將在這裏給出一個綱要。java

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!git

插槽是什麼?

插槽是Vue組件的一種機制,它容許你以一種不一樣於嚴格的父子關係的方式組合組件。插槽爲你提供了一個將內容放置到新位置或使組件更通用的出口。從一個簡單的例子開始:github

// frame.vue
<template>
  <div class="frame">
    <slot></slot>
  </div>
</template>

這個組件最外層是一個div。假設div的存在是爲了圍繞其內容建立一個樣式框架。這個組件能夠通用地用於將框架包圍在wq你想要的任何內容上,來看看它是怎麼用的。這裏的frame組件指的是咱們剛纔作的組件。編程

// app.vue
<template>
  <frame><img src="an-image.jpg"></frame>
</template>

在開始和結束frame標記之間的內容將插入到插槽所在的frame組件中,替換slot標記。這是最基本的方法。還能夠簡單地經過填充指定要放入槽中的默認內容小程序

// frame.vue
<template>
  <div class="frame">
    <slot>若是這裏沒有指定任何內容,這就是默認內容</slot>
  </div>
</template>

因此如今若是咱們這樣使用它:segmentfault

// app.vue
<template>
  <frame />
</template>

若是這裏沒有指定任何內容,這就是默認內容」是默認內容,可是若是像之前那樣使用它,默認文本將被img標記覆蓋。

多個/命名的插槽

能夠向組件添加多個插槽,可是若是這樣作了,那麼除了其中一個以外,其餘全部插槽都須要有名稱。若是有一個沒有名稱的槽,它就是默認槽。下面是如何建立多個插槽:

// titled-frame.vue
<template>
  <div class="frame">
    <header><h2>
      <slot name="header">Title</slot>
    </h2></header>
    <slot>若是這裏沒有指定任何內容,這就是默認內容</slot>
  </div>
</template>

咱們保留了相同的默認槽,但此次咱們添加了一個名爲header的槽,能夠在其中輸入標題,用法以下:

// app.vue
<template>
  <titled-frame>
    <template v-slot:header>
      <!-- The code below goes into the header slot -->
      My Image’s Title
    </template>
    <!-- The code below goes into the default slot -->
    <img src="an-image.jpg">
  </titled-frame>
</template>

就像以前同樣,若是咱們想將內容添加到默認槽中,只需將其直接放在titled-frame組件中。可是,要將內容添加到命名槽中,咱們須要用v-slot指令將代碼包裹在在template標記中。在v-slot以後添加冒號(:),而後寫出要傳遞內容的slot的名稱。

注意,v-slotVue 2.6的新版本,因此若是你使用的是舊版本,則須要閱讀關於不推薦的slot語法的文檔。

做用域插槽

還須要知道的另外一件事是插槽能夠將數據/函數傳遞給他們的孩子。 爲了證實這一點,咱們須要一個徹底不一樣的帶有插槽的示例組件:建立一個組件,該組件將當前用戶的數據提供給其插槽:

// current-user.vue
<template>
  <span>
    <slot v-bind:user="user">
      {{ user.lastName }}
    </slot>
  </span>
</template>

<script>
export default {
  data () {
    return {
      user: ...
    }
  }
}
</script>

該組件有一個名爲user的屬性,其中包含關於用戶的詳細信息。默認狀況下,組件顯示用戶的姓,但請注意,它使用v-bind將用戶數據綁定到slot。這樣,咱們就可使用這個組件向它的後代提供用戶數據

// app.vue
<template>
  <current-user>
    <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template>    
  </current-user>
</template>

爲了訪問傳遞給slot的數據,咱們使用v-slot指令的值指定做用域變量的名稱。

這裏有幾點須要注意:

  • 咱們指定了default的名稱,可是不須要爲默認槽指定名稱。相反,咱們可使用v-slot="slotProps"
  • 不須要使用slotProps做爲名稱,能夠隨便叫它什麼。
  • 若是隻使用默認槽,能夠跳過內部template標記,直接將v-slot指令放到當前current-user上。
  • 可使用對象解構來建立對做用域插槽數據的直接引用,而不是使用單個變量名。換句話說,可使用v-slot="{user}"代替v-slot="slotProps",而後能夠直接使用user而不是slotProps.user

因此,上面的例子能夠這樣重寫

// app.vue
<template>
  <current-user v-slot="{user}">
    {{ user.firstName }}
  </current-user>
</template>

還有幾點要記住:

  • 可使用v-bind指令綁定多個值。
  • 也能夠將函數傳遞到做用域槽。許多庫使用它來提供可重用的函數組件。
  • v-slot 的別名是#。所以,能夠用#header="data" 來代替 v-slot:header="data"。還可使用 #header來代替 v-slot:header(前提:不是做用域插槽時)。對於默認插槽,在使用別名時須要指定默認名稱。換句話說,須要這樣寫 #default="data" 而不是#="data"

能夠從文檔中瞭解更多的細節,但這足以幫助你理解在本文剩下部分中討論的內容。

你能用插槽作什麼?

插槽不是爲了一個目的而構建的,或者至少若是它們是,它們已經超越了最初的意圖,成爲作許多不一樣事物的強大工具。

可重用的模式

組件老是被設計爲可重用的,可是某些模式對於使用單個「普通」組件來實施是不切實際的,由於爲了自定義它,須要的props 數量可能過多或者須要經過props傳遞大部份內容或其它組件。

插槽可用包裹外部的HTML標籤或者組件,並容許其餘HTML或組件放在具名插槽對應名稱的插槽上。

對於的第一個例子,從簡單的東西開始:一個按鈕。假設我們的團隊正在使用 Bootstrap。使用Bootstrap,按鈕一般與基本的「btn」類和指定顏色的類綁定在一塊兒,好比「btn-primary」。你還能夠添加size類,好比'btn-lg'

爲了簡單起見,如今讓咱們假設你的應用使用btnbtn-primarybtn-lg。你不但願老是必須在按鈕上寫下這三個類,或者你不相信新手會記得寫下這三個類。

在這種狀況下,能夠建立一個自動包含全部這三個類的組件,可是如何容許自定義內容? prop 不實用,由於容許按鈕包含各類HTML,所以咱們應該使用一個插槽。

<!-- my-button.vue -->
<template>
  <button class="btn btn-primary btn-lg">
    <slot>Click Me!</slot>
  </button>
</template>

如今咱們能夠在任何地方使用它,不管你想要什麼內容

<!-- 使用 my-button.vue -->
<template>
  <my-button>
    <img src="/img/awesome-icon.jpg"> 我是小智!
  </my-button>
</template>

固然,你能夠選擇比按鈕更大的東西。 堅持使用Bootstrap,讓咱們看一個模態:

<!-- my-modal.vue -->
<template>
<div class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <slot name="header"></slot>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <slot name="body"></slot>
      </div>
      <div class="modal-footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</div>
</template>

如今,使用它:

<!-- 使用 my-modal.vue -->
<template>
  <my-modal>
    <template #header>
      <h5>你們最棒!</h5>
    </template>
    <template #body>
      <p>你們加油</p>
    </template>
    <template #footer>
      <em>你們好樣的!</em>
    </template>
  </my-modal>
</template>

上述類型的插槽用例顯然很是有用,但它能夠作得更多。

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

複用函數

Vue組件並不徹底是關於HTML和CSS的。它們是用JavaScript構建的,因此也是關於函數的。插槽對於一次性建立函數並在多個地方使用功能很是有用。讓咱們回到模態示例並添加一個關閉模態的函數

<!-- my-modal.vue -->
<template>
<div class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <slot name="header"></slot>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <slot name="body"></slot>
      </div>
      <div class="modal-footer">        
        <slot name="footer" :closeModal="closeModal"></slot>
      </div>
    </div>
  </div>
</div>
</template>

<script>
export default {
  //...
  methods: {
    closeModal () {
      // 關閉對話框時,須要作的事情
    }
  }
}
</script>

當使用此組件時,能夠向footer添加一個能夠關閉模態的按鈕。 一般,在Bootstrap模式的狀況下,能夠將data-dismiss =「modal」添加到按鈕來進行關閉。

但咱們但願隱藏Bootstrap 特定的東西。 因此咱們傳遞給他們一個他們能夠調用的函數,這樣使用者就不會知道咱們有使用 Bootstrap 的東西。

<!-- 使用 my-modal.vue -->
<template>
  <my-modal>
    <template #header>
      <h5>Awesome Interruption!</h5>
    </template>
    <template #body>
      <p>你們加油!</p>
    </template>
    <template #footer="{closeModal}">
      <button @click="closeModal">
        點我能夠關閉煩人的對話框
      </button>
    </template>
  </my-modal>
</template>

無渲染組件

最後,能夠利用你所知道的關於使用插槽來傳遞可重用函數的知識,並剝離全部HTML,只使用插槽。這就是無渲染組件的本質:一個只提供函數而不包含任何HTML的組件。

使組件真正無渲染可能有點棘手,由於須要編寫render函數而不是使用模板來消除對根元素的依賴,但它可能並不老是必要的。 來看看一個先使用模板的簡單示例:

<template>
  <transition name="fade" v-bind="$attrs" v-on="$listeners">
    <slot></slot>
  </transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

這是一個無渲染組件的奇怪例子,由於它甚至沒有任何JavaScript。這主要是由於咱們正在建立一個內置無渲染函數的預配置可重用版本:transition

是的,Vue有內置的無渲染組件。這個特殊的例子取自Cristi Jora的一篇關於可重用transition的文章,展現了一種建立無渲染組件的簡單方法,該組件能夠標準化整個應用程序中使用的 transition

對於咱們的另外一個示例,咱們將建立一個組件來處理切換 Promise 的不一樣狀態中顯示的內容: pending、resolved 和 failed。這是一種常見的模式,雖然它不須要不少代碼,可是若是沒有爲了可重用性而提取邏輯,它會使不少組件變得混亂。

<!-- promised.vue -->
<template>
  <span>
    <slot  name="rejected"  v-if="error" :error="error"></slot>
    <slot  name="resolved"  v-else-if="resolved" :data="data"></slot>
    <slot  name="pending"  v-else></slot>
  </span>
</template>

<script>
export  default {
  props: {
    promise:  Promise
  },

  data: () => ({
    resolved:  false,
    data:  null,
    error:  null
  }),  

  watch: {
    promise: {
      handler (promise) {
        this.resolved  =  false
        this.error  =  null

        if (!promise) {
          this.data  =  null
          return
        }

        promise.then(data  => {
          this.data  =  data
          this.resolved  =  true
        })
        .catch(err  => {
          this.error  =  err
          this.resolved  =  true
        })
      },
      immediate:  true
    }
  }
}
</script>

這是怎麼回事,小老弟?首先,請注意,該組件接收一個Promise 類型參數。在watch部分中,監聽promise的變化,當promise發生變化時,清除狀態,而後調用 then 並 catch promise,當 promise 成功完成或失敗時更新狀態。

而後,在模板中,咱們根據狀態顯示一個不一樣的槽。請注意,咱們沒有保持它真正的無渲染,由於咱們須要一個根元素來使用模板。咱們還將dataerror傳遞到相關的插槽範圍。

<template>
  <div>
    <promised :promise="somePromise">
      <template #resolved="{ data }">
        Resolved: {{ data }}
      </template>
      <template #rejected="{ error }">
        Rejected: {{ error }}
      </template>
      <template #pending>
          請求中...
      </template>
    </promised>
  </div>
</template>
...

咱們將somePromise傳遞給無渲染組件。 而後等待它完成,對於 pending 的插槽,顯示「請求中...」。 若是成功,顯示「Resolved:對應的值」。 若是失敗,顯示「已Rejected:失敗的緣由」。 如今咱們再也不須要跟蹤此組件中的promise的狀態,由於該部分被拉出到它本身的可重用組件中。

那麼,咱們能夠作些什麼來繞過promised.vue中的插槽? 要刪除它,咱們須要刪除template部分並向咱們的組件添加render函數:

render () {
  if (this.error) {
    return this.$scopedSlots['rejected']({error: this.error})
  }

  if (this.resolved) {
    return this.$scopedSlots['resolved']({data: this.data})
  }

  return this.$scopedSlots['pending']()
}

這裏沒有什麼太複雜的。咱們只是使用一些if塊來查找狀態,而後返回正確的做用域slot(經過this.$ scopedslot ['SLOTNAME'](…)),並將相關數據傳遞到slot做用域。當你不使用模板時,能夠跳過使用.vue文件擴展名,方法是將JavaScript從script標記中提取出來,而後將其放入.js文件中。在編譯這些Vue文件時,這應該會給你帶來很是小的性能提高。

總結

Vue的插槽將基於組件的開發提高到了一個全新的水平,雖然本文已經展現了許多可使用插槽的好方法,但還有更多的插槽。歡迎留言討論。

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

關於Fundebug

Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用!

相關文章
相關標籤/搜索