淺嘗Vue.js元件(二)

冷星1024發表於2019-03-29

插槽(Slot)

定義一個名child子元件,為該子元件新增內容應該在子元件的template中定義,直接在父元件的<child>標籤中定義的內容不會被渲染。

在子元件中通過加入<slot>元素佔位,便能夠渲染父元件中子元件標籤中的內容了。

插槽內容

  • 任何模版程式碼
  • HTML程式碼
  • 其他元件

插槽可以有預設內容,當在父元件中沒有提供內容的時候,來進行顯示。

<!-- submit-button -->
<button type="submit">
  <slot>Submit</slot>
</button>


1.
<submit-button></submit-button>
⬇️ 
<button type="submit">
  Submit
</button>

2.
<submit-button>
  Save
</submit-button>
⬇️
<button type="submit">
  Save
</button>
複製程式碼

作用域

父級模板裡的所有內容都是在父級作用域中編譯的;子模板裡的所有內容都是在子作用域中編譯的。

具名插槽

試想,我們有一個帶有如下模版的<base-layout>元件

<div class="container">
  <header>
    <!-- 我們希望把頁頭放這裡 -->
  </header>
  <main>
    <!-- 我們希望把主要內容放這裡 -->
  </main>
  <footer>
    <!-- 我們希望把頁尾放這裡 -->
  </footer>
</div>
複製程式碼

可以看到,在元件中顯示的內容是劃分不同的部位的,這個時候就需要使用到<slot>元素的一個特有的屬性:name來實現了。這個特性可以用來定義額外的插槽。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
複製程式碼

一個不帶 name 的 <slot> 出口會帶有隱含的名字“default”。

在向具名插槽提供內容的時候,我們可以在一個 <template> 元素上使用 v-slot 指令,並以 v-slot 的引數的形式提供其名稱:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
複製程式碼

現在 <template> 元素中的所有內容都將會被傳入相應的插槽。任何沒有被包裹在帶有 v-slot<template> 中的內容都會被視為預設插槽的內容。

當然,也可以將預設插槽的內容通過v-slot:default包裹起來。

v-slot 只能新增在一個 <template>

作用域插槽

當我們希望能夠讓插槽內容能夠訪問子元件中才有的資料時,我們可以將資料作為一個<slot>元素的特性繫結上去

<span>
  <slot v-bind:user="user">
    {{ user.name }}
  </slot>
</span>
複製程式碼

繫結在<slot>元素上的特性被稱為插槽prop。此時我們在父元件中通過給v-slot帶一個值來定義我們提供的插槽prop的名字。

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.age }}
  </template>
</current-user>
複製程式碼

獨佔預設插槽的縮寫語法

當被提供的內容只有預設插槽時,上面的寫法可以被簡化來寫

<!-- 簡化版 -->
<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

<!-- 終極簡化版 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>
複製程式碼

需要注意兩點:

  • 簡化寫法不能和具名插槽混用,作用域不明確
  • 出現多個插槽時,所有插槽都使用完整的基於<template>語法

解構插槽Prop

作用域插槽的內部工作原理是將你的插槽內容包括在一個傳入單個引數的函式裡:

function (slotProps) {
  // 插槽內容
}
複製程式碼

這意味著 v-slot 的值實際上可以是任何能夠作為函式定義中的引數的 JavaScript 表示式。所以在支援的環境下 (單檔案元件現代瀏覽器),你也可以使用 ES2015 解構來傳入具體的插槽 prop,如下:

<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>
⬇️
<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>
複製程式碼

使用場景舉例

插槽 prop 允許我們將插槽轉換為可複用的模板,這些模板可以基於輸入的 prop 渲染出不同的內容。

這在設計封裝資料邏輯同時允許父級元件自定義部分佈局的可複用元件時是最有用的。

動態插槽名

動態指令引數也可以用在 v-slot 上,來定義動態的插槽名

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>
複製程式碼

具名插槽縮寫

v-slot可以縮寫為#

縮寫方式只有在有引數的時候才可以使用

<!-- 這樣會觸發一個警告 -->
<current-user #="{ user }">
  {{ user.firstName }}
</current-user>

<!-- 這樣是正確的 -->
<current-user #default="{ user }">
  {{ user.firstName }}
</current-user>
複製程式碼

動態元件&keep-alive

當在這些元件之間切換的時候,你有時會想保持這些元件的狀態,以避免反覆重渲染導致的效能問題。為了解決這個問題,我們可以用一個<keep-alive>元素將動態元件包裹起來

<!-- 失活的元件將會被快取!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
複製程式碼

注意這個 <keep-alive> 要求被切換到的元件都有自己的名字,不論是通過元件的 name 選項還是區域性/全域性註冊。

更加詳細的說明在我們之後的實戰過程中遇到的話,再進行專門解說。

非同步元件

在實際的專案過程中,我們往往會將一系列的功能分割成一個個小的程式碼塊,希望只有在需要的時候才去載入。為了達成這個目的,Vue允許我們以一個工廠函式的方式定義我們的元件,這個工廠函式會非同步解析元件的定義。

Vue只有在這個元件需要渲染的時候才會觸發這個工廠函式,而且會把結果快取起來供之後使用。

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回撥傳遞元件定義
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})
複製程式碼

其實,這個過程有些類似於我們設計一個非同步函式,這個工廠函式會收到一個resolve回撥,這個回撥在我們從伺服器獲取到元件定義的時候被呼叫,當載入失敗的時候我們也可以呼叫reject(reason)。

一個推薦的做法是非同步元件和webpack的code-splitting功能結合使用

Vue.component('async-webpack-example', function (resolve) {
  // 這個特殊的 `require` 語法將會告訴 webpack
  // 自動將你的構建程式碼切割成多個包,這些包
  // 會通過 Ajax 請求載入
  require(['./my-async-component'], resolve)
})
複製程式碼

同樣,也可以在工廠函式中返回一個Promise

Vue.component(
  'async-webpack-example',
  // 這個 `import` 函式會返回一個 `Promise` 物件。
  () => import('./my-async-component')
)
複製程式碼

處理載入狀態

上面的工廠函式可以返回一個下面格式的物件

const AsyncComponent = () => ({
  // 需要載入的元件 (應該是一個 `Promise` 物件)
  component: import('./MyComponent.vue'),
  // 非同步元件載入時使用的元件
  loading: LoadingComponent,
  // 載入失敗時使用的元件
  error: ErrorComponent,
  // 展示載入時元件的延時時間。預設值是 200 (毫秒)
  delay: 200,
  // 如果提供了超時時間且元件載入也超時了,
  // 則使用載入失敗時使用的元件。預設值是:`Infinity`
  timeout: 3000
})
複製程式碼

小結

本篇我們主要圍繞著Vue元件中的插槽和相關動態元件、非同步元件的內容進行了梳理。通過這兩篇的整理,我們對於Vue元件有了一個比較整體的瞭解。後續我們會在實戰過程中針對具體的點再進行詳細的說明。

相關文章