1. 程式人生 > >詳解Vue的slot新用法

詳解Vue的slot新用法

摘要: 理解Vue插槽。

Fundebug經授權轉載,版權歸原作者所有。

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

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

如果你是Vue的新手,或者還沒有看到2.6版的變化,請繼續閱讀。也許學習插槽的最佳資源是Vue自己的文件,但是我將在這裡給出一個綱要。

想閱讀更多優質文章請猛戳GitHub部落格,一年百來篇優質文章等著你!

插槽是什麼?

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

// 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>

所以現在如果我們這樣使用它:

// 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對1、微脈、青團社等眾多品牌企業。歡迎