【Vue3】keep-alive 缓存组件

当在 Vue.js 中使用 <keep-alive> 组件时,它将会缓存动态组件,而不是每次渲染都销毁和重新创建它们。这对于需要在组件间快速切换并且保持组件状态的情况非常有用。

<keep-alive> 只能包含(或者说只能渲染)一个子组件,如果需要包含多个子组件,需要用 v-if 选择某个确定的组件进行渲染。也可以使用 <template> 或其他组件标签将它们包裹起来,再通过 v-if 选择确定的某个组件进行渲染。因此 keep-alive 标签里面有且只有一个子节点(或者说只能渲染一个子节点)。这些子组件可以通过动态组件或者其他方式进行切换。

当一个被 <keep-alive> 组件包裹的组件被切换出去时,它的状态将会被保留在内存中,而不会被销毁。当组件再次被切换回来时,它会从缓存中直接取出,并且保持之前的状态。

1. keep-alive 和 v-show 的区别

  • 作用:

<keep-alive>:用于缓存组件,以避免每次组件切换时销毁和重新创建实例。它会保留组件的状态,以便在需要时直接从缓存中取出。

v-show:用于根据条件动态显示或隐藏组件。它通过控制 display CSS 属性来决定组件是否在 DOM 中渲染。

  • 使用场景:

<keep-alive>:适用于需要在组件间快速切换并保持组件状态的场景。例如,当你有一个包含表单的编辑页面,在切换到其他页面并返回时,希望保留用户已输入的数据。

v-show:适用于根据某些条件动态显示或隐藏单个组件。例如,根据用户权限动态显示不同的导航菜单。

  • 对组件实例的影响:

<keep-alive>:保留组件的状态和实例,只是保存虚拟 DOM ,以便在需要时直接从缓存中取出。组件的生命周期钩子函数 activated 和 deactivated 也会在缓存和激活时触发。

v-show:每次切换显示或隐藏时,组件实例会保持不变,存在于真实 DOM ,并且会触发相应的生命周期钩子函数 beforeUpdate 和 updated。

2. keep-alive 使用案例

App.vue

html 复制代码
<template>
  <el-button type="primary" @click="flag = !flag"> 切换组件</el-button>
  <!-- [] 里面名称是原本的组件名 -->
  <!-- 
    props介绍:
    :exclude="['A']"  不缓存A组件
    :include="['A']"  缓存A组件
    :max="2"  最多缓存2个,如有多个从后往前算起
   -->
  <keep-alive :exclude="['A']">
    <AVue v-if="flag"></AVue>
    <B v-else></B>
  </keep-alive>

</template>

<script setup lang="ts">
import { ref } from 'vue';
import AVue from './components/A.vue'
import B from './components/B.vue'
const flag = ref<boolean>(true)
</script>

<style scoped></style>

A.vue

html 复制代码
<template>
  <h1 style="margin: 10px;">我是A组件</h1>
  <el-form :model="form" label-width="120px">
    <el-form-item label="Activity name">
      <el-input v-model="form.name" />
    </el-form-item>
    <el-form-item label="Activity zone">
      <el-select v-model="form.region" placeholder="please select your zone">
        <el-option label="Zone one" value="shanghai" />
        <el-option label="Zone two" value="beijing" />
      </el-select>
    </el-form-item>
    <el-form-item label="Activity time">
      <el-col :span="11">
        <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%" />
      </el-col>
      <el-col :span="2" class="text-center">
        <span class="text-gray-500">-</span>
      </el-col>
      <el-col :span="11">
        <el-time-picker v-model="form.date2" placeholder="Pick a time" style="width: 100%" />
      </el-col>
    </el-form-item>
    <el-form-item label="Instant delivery">
      <el-switch v-model="form.delivery" />
    </el-form-item>
    <el-form-item label="Activity type">
      <el-checkbox-group v-model="form.type">
        <el-checkbox label="Online activities" name="type" />
        <el-checkbox label="Promotion activities" name="type" />
        <el-checkbox label="Offline activities" name="type" />
        <el-checkbox label="Simple brand exposure" name="type" />
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Resources">
      <el-radio-group v-model="form.resource">
        <el-radio label="Sponsor" />
        <el-radio label="Venue" />
      </el-radio-group>
    </el-form-item>
    <el-form-item label="Activity form">
      <el-input v-model="form.desc" type="textarea" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="onSubmit">Create</el-button>
      <el-button>Cancel</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive } from 'vue'

// do not use same name with ref
const form = reactive({
  name: '',
  region: '',
  date1: '',
  date2: '',
  delivery: false,
  type: [],
  resource: '',
  desc: '',
})

const onSubmit = () => {
  console.log('submit!')
}
</script>

B.vue

html 复制代码
<template>
  <h1 style="margin: 10px;">我是B组件</h1>
  <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm" :size="formSize"
    status-icon>
    <el-form-item label="Activity name" prop="name">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item label="Activity zone" prop="region">
      <el-select v-model="ruleForm.region" placeholder="Activity zone">
        <el-option label="Zone one" value="shanghai" />
        <el-option label="Zone two" value="beijing" />
      </el-select>
    </el-form-item>
    <el-form-item label="Activity count" prop="count">
      <el-select-v2 v-model="ruleForm.count" placeholder="Activity count" :options="options" />
    </el-form-item>
    <el-form-item label="Activity time" required>
      <el-col :span="11">
        <el-form-item prop="date1">
          <el-date-picker v-model="ruleForm.date1" type="date" label="Pick a date" placeholder="Pick a date"
            style="width: 100%" />
        </el-form-item>
      </el-col>
      <el-col class="text-center" :span="2">
        <span class="text-gray-500">-</span>
      </el-col>
      <el-col :span="11">
        <el-form-item prop="date2">
          <el-time-picker v-model="ruleForm.date2" label="Pick a time" placeholder="Pick a time" style="width: 100%" />
        </el-form-item>
      </el-col>
    </el-form-item>
    <el-form-item label="Instant delivery" prop="delivery">
      <el-switch v-model="ruleForm.delivery" />
    </el-form-item>
    <el-form-item label="Activity type" prop="type">
      <el-checkbox-group v-model="ruleForm.type">
        <el-checkbox label="Online activities" name="type" />
        <el-checkbox label="Promotion activities" name="type" />
        <el-checkbox label="Offline activities" name="type" />
        <el-checkbox label="Simple brand exposure" name="type" />
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Resources" prop="resource">
      <el-radio-group v-model="ruleForm.resource">
        <el-radio label="Sponsorship" />
        <el-radio label="Venue" />
      </el-radio-group>
    </el-form-item>
    <el-form-item label="Activity form" prop="desc">
      <el-input v-model="ruleForm.desc" type="textarea" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)">
        Create
      </el-button>
      <el-button @click="resetForm(ruleFormRef)">Reset</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'

interface RuleForm {
  name: string
  region: string
  count: string
  date1: string
  date2: string
  delivery: boolean
  type: string[]
  resource: string
  desc: string
}

const formSize = ref('default')
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
  name: '',
  region: '',
  count: '',
  date1: '',
  date2: '',
  delivery: false,
  type: [],
  resource: '',
  desc: '',
})

const rules = reactive<FormRules<RuleForm>>({
  name: [
    { required: true, message: 'Please input Activity name', trigger: 'blur' },
    { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
  ],
  region: [
    {
      required: true,
      message: 'Please select Activity zone',
      trigger: 'change',
    },
  ],
  count: [
    {
      required: true,
      message: 'Please select Activity count',
      trigger: 'change',
    },
  ],
  date1: [
    {
      type: 'date',
      required: true,
      message: 'Please pick a date',
      trigger: 'change',
    },
  ],
  date2: [
    {
      type: 'date',
      required: true,
      message: 'Please pick a time',
      trigger: 'change',
    },
  ],
  type: [
    {
      type: 'array',
      required: true,
      message: 'Please select at least one activity type',
      trigger: 'change',
    },
  ],
  resource: [
    {
      required: true,
      message: 'Please select activity resource',
      trigger: 'change',
    },
  ],
  desc: [
    { required: true, message: 'Please input activity form', trigger: 'blur' },
  ],
})

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log('submit!')
    } else {
      console.log('error submit!', fields)
    }
  })
}

const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}

const options = Array.from({ length: 10000 }).map((_, idx) => ({
  value: `${idx + 1}`,
  label: `${idx + 1}`,
}))
</script>


3. 因注释导致的意外错误

写入注释:

报错:

因此 keep-alive 组件内不要使用注释,会被解析为子节点

解决方法:

  • keep-alive 内部不写注释

  • 使用 <template> 或其他组件标签将它们包裹起来

    • 此时 可以认为 利用了 keep-alive 标签里面有且只有一个子节点(或者说只能渲染一个子节点)
html 复制代码
  <keep-alive :exclude="['A']">
      <template v-if="flag">
        <!-- [] 里面名称是原本的组件名 -->
        <AVue></AVue>
      </template>
      <template v-else>
        <B></B>
      </template>
  </keep-alive>

4. keep-alive 生命周期

<keep-alive> 组件提供了以下两个主要的生命周期钩子函数来管理缓存的组件:

  • activated:被缓存的组件激活时调用。

  • deactivated:被缓存的组件停用时调用。

App.vue

html 复制代码
<template>
  <el-button type="primary" @click="flag = !flag"> 切换组件</el-button>

  <!-- 
    props介绍:
    :exclude="['A']"  不缓存A组件
    :include="['A']"  缓存A组件
    :max="2"  最多缓存2个,如有多个从后往前算起
   -->
  <keep-alive :exclude="['B']">
      <AVue v-if="flag"></AVue>
      <B v-else></B>
  </keep-alive>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import AVue from './components/A.vue'
import B from './components/B.vue'

const flag = ref<boolean>(true)
</script>

<style scoped></style>

A.vue

html 复制代码
<template>
  <h1 style="margin: 10px;">我是A组件</h1>
  <el-form :model="form" label-width="120px">
    <el-form-item label="Activity name">
      <el-input v-model="form.name" />
    </el-form-item>
    <el-form-item label="Activity zone">
      <el-select v-model="form.region" placeholder="please select your zone">
        <el-option label="Zone one" value="shanghai" />
        <el-option label="Zone two" value="beijing" />
      </el-select>
    </el-form-item>
    <el-form-item label="Activity time">
      <el-col :span="11">
        <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%" />
      </el-col>
      <el-col :span="2" class="text-center">
        <span class="text-gray-500">-</span>
      </el-col>
      <el-col :span="11">
        <el-time-picker v-model="form.date2" placeholder="Pick a time" style="width: 100%" />
      </el-col>
    </el-form-item>
    <el-form-item label="Instant delivery">
      <el-switch v-model="form.delivery" />
    </el-form-item>
    <el-form-item label="Activity type">
      <el-checkbox-group v-model="form.type">
        <el-checkbox label="Online activities" name="type" />
        <el-checkbox label="Promotion activities" name="type" />
        <el-checkbox label="Offline activities" name="type" />
        <el-checkbox label="Simple brand exposure" name="type" />
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Resources">
      <el-radio-group v-model="form.resource">
        <el-radio label="Sponsor" />
        <el-radio label="Venue" />
      </el-radio-group>
    </el-form-item>
    <el-form-item label="Activity form">
      <el-input v-model="form.desc" type="textarea" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="onSubmit">Create</el-button>
      <el-button>Cancel</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive,onMounted,onUnmounted,onDeactivated,onActivated } from 'vue'

// do not use same name with ref
const form = reactive({
  name: '',
  region: '',
  date1: '',
  date2: '',
  delivery: false,
  type: [],
  resource: '',
  desc: '',
})

const onSubmit = () => {
  console.log('submit!')
}

onMounted(() => {
  console.log('初始化')
}),
onActivated(() => {
  console.log('keep-alive初始化')
})
onDeactivated(() => {
  console.log('keep-alive销毁')
})
onUnmounted(() => {
  console.log('销毁')
})
</script>

B.vue

html 复制代码
<template>
  <h1 style="margin: 10px;">我是B组件</h1>
  <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm" :size="formSize"
    status-icon>
    <el-form-item label="Activity name" prop="name">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item label="Activity zone" prop="region">
      <el-select v-model="ruleForm.region" placeholder="Activity zone">
        <el-option label="Zone one" value="shanghai" />
        <el-option label="Zone two" value="beijing" />
      </el-select>
    </el-form-item>
    <el-form-item label="Activity count" prop="count">
      <el-select-v2 v-model="ruleForm.count" placeholder="Activity count" :options="options" />
    </el-form-item>
    <el-form-item label="Activity time" required>
      <el-col :span="11">
        <el-form-item prop="date1">
          <el-date-picker v-model="ruleForm.date1" type="date" label="Pick a date" placeholder="Pick a date"
            style="width: 100%" />
        </el-form-item>
      </el-col>
      <el-col class="text-center" :span="2">
        <span class="text-gray-500">-</span>
      </el-col>
      <el-col :span="11">
        <el-form-item prop="date2">
          <el-time-picker v-model="ruleForm.date2" label="Pick a time" placeholder="Pick a time" style="width: 100%" />
        </el-form-item>
      </el-col>
    </el-form-item>
    <el-form-item label="Instant delivery" prop="delivery">
      <el-switch v-model="ruleForm.delivery" />
    </el-form-item>
    <el-form-item label="Activity type" prop="type">
      <el-checkbox-group v-model="ruleForm.type">
        <el-checkbox label="Online activities" name="type" />
        <el-checkbox label="Promotion activities" name="type" />
        <el-checkbox label="Offline activities" name="type" />
        <el-checkbox label="Simple brand exposure" name="type" />
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Resources" prop="resource">
      <el-radio-group v-model="ruleForm.resource">
        <el-radio label="Sponsorship" />
        <el-radio label="Venue" />
      </el-radio-group>
    </el-form-item>
    <el-form-item label="Activity form" prop="desc">
      <el-input v-model="ruleForm.desc" type="textarea" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)">
        Create
      </el-button>
      <el-button @click="resetForm(ruleFormRef)">Reset</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'

interface RuleForm {
  name: string
  region: string
  count: string
  date1: string
  date2: string
  delivery: boolean
  type: string[]
  resource: string
  desc: string
}

const formSize = ref('default')
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
  name: '',
  region: '',
  count: '',
  date1: '',
  date2: '',
  delivery: false,
  type: [],
  resource: '',
  desc: '',
})

const rules = reactive<FormRules<RuleForm>>({
  name: [
    { required: true, message: 'Please input Activity name', trigger: 'blur' },
    { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
  ],
  region: [
    {
      required: true,
      message: 'Please select Activity zone',
      trigger: 'change',
    },
  ],
  count: [
    {
      required: true,
      message: 'Please select Activity count',
      trigger: 'change',
    },
  ],
  date1: [
    {
      type: 'date',
      required: true,
      message: 'Please pick a date',
      trigger: 'change',
    },
  ],
  date2: [
    {
      type: 'date',
      required: true,
      message: 'Please pick a time',
      trigger: 'change',
    },
  ],
  type: [
    {
      type: 'array',
      required: true,
      message: 'Please select at least one activity type',
      trigger: 'change',
    },
  ],
  resource: [
    {
      required: true,
      message: 'Please select activity resource',
      trigger: 'change',
    },
  ],
  desc: [
    { required: true, message: 'Please input activity form', trigger: 'blur' },
  ],
})

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log('submit!')
    } else {
      console.log('error submit!', fields)
    }
  })
}

const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}

const options = Array.from({ length: 10000 }).map((_, idx) => ({
  value: `${idx + 1}`,
  label: `${idx + 1}`,
}))
</script>


相关推荐
罗_三金1 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
冯宝宝^1 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
程序员小羊!1 小时前
前端框架对比和选择
前端框架
微刻时光1 小时前
Redis集群知识及实战
数据库·redis·笔记·学习·程序人生·缓存
cc蒲公英2 小时前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel
森叶2 小时前
Electron-vue asar 局部打包优化处理方案——绕开每次npm run build 超级慢的打包问题
vue.js·electron·npm
丁总学Java2 小时前
如何使用 maxwell 同步到 redis?
数据库·redis·缓存
小小竹子2 小时前
前端vue-实现富文本组件
前端·vue.js·富文本
青稞儿3 小时前
面试题高频之token无感刷新(vue3+node.js)
vue.js·node.js
小菜yh3 小时前
关于Redis
java·数据库·spring boot·redis·spring·缓存