iT邦幫忙

2021 iThome 鐵人賽

DAY 15
3
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 15

不只懂 Vue 語法:請說明 keep-alive 以及 is 屬性的作用?

問題回答

<keep-alive> 的作用是緩存一個元件的資料狀態,即使它被切換掉,不再呈現在畫面上時,它的資料狀態依然會把存起來,並不會像一般元件那樣被銷毀。

另外,is 屬性的作用是動態載入元件,實現切換元件的效果。最常見的用法是在<component />透過 v-bind 使用is 屬性來切換顯示元件:<component :is="...">。而 is 屬性也可以用在 HTML 標籤上。

以下會再作詳細解說。

什麼情景要用 keep-alive

常用情景包括:

  • 減少呼叫 API 的次數。例如該元件每次重新渲染時都要呼叫 API 取資料,除了加重後端負擔,也會因為等候時間而影響使用者體驗。
  • 多步式表單填寫。當使用者按下「上一步」按鈕時,需要呈現在前一個元件裏他所填寫的資料。
  • 官方例子提到的 Tab 標籤切換內容。當按下其中一個 tab,再按回上一個 tab,該元件的狀態應該要被保留,而不是被重新渲染。

keep-alive 語法重點

最基本使用方法

<keep-alive>
    <component />
</keep-alive>

keep-alive 是一個抽象元件,即是它不會被渲染成 DOM 節點掛載在畫面上。而被 <keep-alive> 所包著的元件的資料狀態會被緩存起來。

沒有完整生命週期

該元件在只會跑一次生命週期,之後只會跑 activateddeactivated 這兩個生命週期,代替 mountedunmounted。我們可以在元件裏使用此兩個 lifecycle hook:

activated() {
    ...
}
deactivated() {
    ...
}

能接收 3 個 props

<keep-alive> 接收 3 個 props,讓我們更客製化指定要緩存什麼元件。

<keep-alive include="" exclude="" max="">
</keep-alive>

官方文件清楚說明每個 prop 的型別和作用:

詳細語法就不在此說明,有需要可再查看官方文件

謹記:如果使用 include 和 exclude props 來指定元件名稱時,該元件需要有 name 屬性才會生效。

使用 include 指定元件 a 需要被緩存:

<keep-alive>
    <component include="a" />
</keep-alive>

該元件要有 name 屬性:

export default {
    name: 'a'
}

router-view 與 keep-alive

同樣地, router-view 也是一個元件,一樣可以被 keep-alive 包起來,把此 router-view 裏的所有元件都緩存起來:

<router-view v-slot="{ Component }">
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
</router-view>

目前 Vue 已經不容許 <router-view> 直接寫在 <keep-alive><transition> 裏面,需要使用 v-slot

如果只是要緩存指定的元件,做法有兩種:

  • 使用以上提到的 includeexclude
  • 為需要緩存的元件,在它的路由物件加上 meta 屬性,再在裏面自定義屬性,讓我們在 router-view 做判斷哪些元件要被緩存。

第一種做法:

Example.vue

export default {
    name: 'example'
}

App.vue

<router-view v-slot="{ Component }">
    <keep-alive include="example">
      <component :is="Component" />
      // 只有 Example 元件會被緩存
    </keep-alive>
</router-view>

第二種做法:

router/index.js

export default [
  {
    path: '/example',
    component: Example,
    meta: {
      keepAlive: true
    }
  }
]

App.vue

<router-view v-if="!$route.meta?.keepAlive" />
<router-view v-slot="{ Component }">
    <keep-alive>
      <component v-if="$route.meta?.keepAlive" :is="Component" />
    </keep-alive>
</router-view>

注意,目前 Vue 已經不容許 <router-view> 直接寫在 <keep-alive><transition> 裏面,因此以下寫法會跳錯

<template>
  <router-view v-if="!$route.meta.keepAlive" />
  <keep-alive>
    <router-view v-if="$route.meta.keepAlive">
    </router-view>
  </keep-alive>
</template>

router-view 與 keep-alive 程式碼示範

以下程式碼示範了第二種方法,即是在路由裏設定 meta (自定義屬性)來判斷是否要套用 keep-alive。例子中的 A 元件,你輸入的資料後再跳回 Home,之後再次進入 A 元件,會發現資料有被緩存。但 B 元件則不會被緩存。

https://codesandbox.io/s/router-view-yu-keep-alive-mbcbf?file=/src/router/index.js

結果示範

多步驟式表單示範

以下會用一個多步驟式表單示範如何使用 keep-alive 以及 is 屬性,做法是參考此文章提及的情景需求。

完整程式碼

https://codesandbox.io/s/keep-alive-duo-bu-biao-dan-li-zi-21mfo?file=/src/App.vue:28-253

目前需求是:

  • 完成一個有三個步驟的表單填寫,前兩個步驟(Step 1、Step 2)有表單讓使用者寫入資料,Step 3 是確認資料頁面,讓使用者確認之前所填寫的資料。
  • 使用者能夠按「上一頁」按鈕,查看之前所填寫的資料。
  • 當使用者按了「上一頁」按鈕,到達上一頁,之後再按下「下一頁」按鈕跳到下一頁,此頁面將不會殘留之前所填寫的資料。

開發重點

分拆 3 個元件來放表單和確認頁面,並使用動態切換元件來呈現

App.vue

<component :is="`Step${currentStep}`"/>
import Step1 from "@/components/Step1.vue";
import Step2 from "@/components/Step2.vue";
import Step3 from "@/components/Step3.vue";

export default {
    components: {
        Step1,
        Step2,
        Step3,
    },
    data() {
        return {
          currentStep: 1,
        };
    }
}

在每個元件綁上所需的事件,用來傳送資料回到父元件 App.vue。以 Step1 元件為例:

Step1.vue

<template>
  <h1>Step 1</h1>
  <div>
    <label for="name">Name:</label>
    <input v-model="name" type="text" id="name" />
  </div>
  <div>
    <label for="age">Age:</label>
    <input v-model="age" type="number" id="age" />
  </div>
  <button @click="next()">Next</button>
</template>

export default {
  // 有 name 屬性,keep-alive 的 include 才會有效
  name: "Step1",
  // 不接收父元件傳來的 inputData prop
  inheritAttrs: false,
  data() {
    return {
      name: "",
      age: 0,
    };
  },
  methods: {
    next() {
      this.$emit("change-step", 2);
      this.$emit("update-data", {
        name: this.name,
        age: this.age,
      });
    },
  },
  activated() {
    console.log("Step 1 is activate!");
  },
  deactivated() {
    console.log("Step 1 is deactivated!");
  },
};

有些重點要注意:

  • 這裏刻意示範了 activateddeactivated 此兩個 lifecycle hook。
  • 使用 inheritAttrs: false。因為在 Step 2 元件裏沒有根節點,所以 Vue 不曉得要把父元件的所有屬性傳給 Step 2 裏哪個節點,因此解決方法有兩種,一是設定 inheritAttrs: false,直接關掉接收父元件的屬性(因為 Step 2 沒有用到父元件傳來的 props)。二是把 <template> 裏所有內容都用一個 div 包起來(在 codesandbox 的示範 code 中會有詳細註解解釋)。
  • 按下「下一頁」時,會 emit 'change-step' 此事件,觸發父元件修改當前的 step。

按下「上一頁」時資料會被緩存的邏輯

按目前的需求,即是說:

在 Step 1 時:
    - 要緩存 Step 1
在 Step 2 時:
    - 要緩存 Step 1, Step 2
在 Step 3 時:
    - 同樣要緩存 Step 1, Step 2,但不用緩存 Step 3

所以,當前 Step 的數值,以及它之前的頁面是需要被緩存。因此,可以利用此特性,使用 computed 回傳一個陣列:

computed: {
    keepAliveComponents() {
      let alive = [];
      for (let i = 0; i < this.currentStep; i++) {
        // Step 3 是確認頁面,不用緩存
        if (i === 2) return alive;
        alive.push(`Step${i + 1}`);
      }
      return alive;
    }
}

因此,每次切換元件,透過動態綁定 :include="keepAliveComponents",計算什麼元件需要被緩存:

App.vue

<keep-alive :include="keepAliveComponents">
  <component
    :is="`Step${currentStep}`"
    :inputData="inputData"
    @change-step="changeStep"
    @update-data="updateData"
  />
</keep-alive>

結果示範

總結

  • keep-alive 會緩存一個元件的資料狀態。
  • is 屬性經常會與 v-bind 一起使用,作用是動態切換元件。
  • keep-alive 接收 3 個 props,包括 include、exclude、max,指定要緩存的元件或緩存元件數量。
  • 如果使用 include 和 exclude props 來指定元件名稱時,謹記該元件需要有 name 屬性才會生效。

參考資料

[Vue] 跟著 Vue 闖蕩前端世界 - 13 使用 keep-alive 保留表單狀態
vue-router 之 keep-alive


上一篇
不只懂 Vue 語法:為什麼要用 Vuex? Vuex 基本架構是怎樣?
下一篇
不只懂 Vue 語法:為什麼需要使用 $nextTick ?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言