不只懂 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


<<:  【Day9】AddInvitationFragment(上)

>>:  Day09 React Props- Array.map()

Day14 v-cloak与v-pre

今天再多来看看两个Vue的指令,v-cloak与v-pre v-cloak 使用v-cloak的原因...

伪类与伪元素-30天学会HTML+CSS,制作精美网站

昨天介绍了各种选择器,今天介绍伪类及伪元素样式设定,可以让画面有更多的样式变化,也减少html co...

Facade 外观模式

在 Structural patterns 当中,最後要来谈的是外观模式。 外观模式提供我们一个简单...

【从零开始的Swift开发心路历程-Day7】简易调色盘Part3(完)

昨天我们做到ImageView及TextField会根据Slider的左右滑动而改变颜色及数值,但是...

【Day04-档案】你知道Excel最大可以开多少笔资料吗?

前一天我们介绍了用来资料处理最基本的pandas套件 那今天我们则是来谈一下不同的档案类型 我们都知...