MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue Keep-Alive 实际项目中的典型应用场景与案例分析

2022-01-091.2k 阅读

Vue Keep - Alive 的原理及基础知识

在深入探讨 Vue Keep - Alive 在实际项目中的应用场景与案例之前,我们先来了解一下它的基本原理和基础知识。

Vue Keep - Alive 的本质

Vue 的 keep - alive 是一个抽象组件,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。它的主要作用是在组件切换过程中,将组件状态保留在内存中,避免重复渲染DOM,从而提高应用的性能。

如何使用 Vue Keep - Alive

在 Vue 模板中使用 keep - alive 非常简单。假设我们有一个组件 MyComponent,我们可以这样包裹它:

<template>
  <div>
    <keep - alive>
      <MyComponent></MyComponent>
    </keep - alive>
  </div>
</template>

MyComponentkeep - alive 包裹后,在它被切换出去时,不会被销毁,而是被缓存起来。下次再次切换到该组件时,直接从缓存中取出并渲染,这大大提升了组件的切换效率。

相关生命周期钩子

  1. activated:当被 keep - alive 缓存的组件激活时调用。这个钩子可以用来做一些组件激活时的初始化操作,比如重新请求数据等。
export default {
  name: 'MyComponent',
  activated() {
    console.log('组件被激活了');
  }
}
  1. deactivated:当被 keep - alive 缓存的组件停用时调用。可以在这个钩子中做一些清理工作,例如取消定时器等。
export default {
  name: 'MyComponent',
  deactivated() {
    console.log('组件停用了');
  }
}

Vue Keep - Alive 在实际项目中的典型应用场景

页面缓存,提升用户体验

  1. 场景描述 在一个多页面的应用中,比如一个电商 APP,用户在商品列表页浏览商品,点击进入商品详情页查看商品详细信息,然后返回商品列表页。如果没有使用 keep - alive,每次返回商品列表页时,列表页都需要重新渲染,包括重新请求数据、重新构建 DOM 结构等操作,这会导致页面有短暂的卡顿,影响用户体验。而使用 keep - alive 可以将商品列表页缓存起来,当用户返回时,直接从缓存中取出列表页,瞬间展示,大大提升了用户体验。

  2. 代码示例 假设我们有一个商品列表组件 ProductList 和商品详情组件 ProductDetail。在路由配置中,我们这样设置:

import Vue from 'vue';
import Router from 'vue - router';
import ProductList from '@/components/ProductList';
import ProductDetail from '@/components/ProductDetail';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/productList',
      name: 'ProductList',
      component: ProductList
    },
    {
      path: '/productDetail/:id',
      name: 'ProductDetail',
      component: ProductDetail
    }
  ]
});

App.vue 中,我们使用 keep - alive 包裹 ProductList 组件:

<template>
  <div id="app">
    <keep - alive>
      <router - view v - if="$route.name === 'ProductList'"></router - view>
    </keep - alive>
    <router - view v - if="$route.name === 'ProductDetail'"></router - view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

ProductList 组件中,我们可以模拟请求数据的操作:

export default {
  name: 'ProductList',
  data() {
    return {
      products: []
    };
  },
  created() {
    this.fetchProducts();
  },
  methods: {
    fetchProducts() {
      // 模拟异步请求
      setTimeout(() => {
        this.products = [
          { id: 1, name: '商品1' },
          { id: 2, name: '商品2' }
        ];
      }, 1000);
    }
  }
}

当用户从商品详情页返回商品列表页时,由于 ProductListkeep - alive 缓存,不会重新调用 created 钩子,也就不会重新请求数据,直接展示之前缓存的列表。

表单场景,保留用户输入状态

  1. 场景描述 在一个复杂的表单填写页面,用户可能需要填写多个字段,并且可能会在不同的表单步骤之间切换。例如,在一个注册表单中,第一步填写基本信息,第二步填写联系方式,第三步填写兴趣爱好等。如果用户在填写完第一步后,切换到第二步,然后又返回第一步,如果没有 keep - alive,第一步填写的内容会丢失,用户需要重新填写,这是非常不友好的。通过 keep - alive 可以保留表单组件的状态,让用户返回时看到之前填写的内容。

  2. 代码示例 假设我们有一个注册表单组件 RegisterForm,它包含多个子组件,分别对应不同的表单步骤。这里我们以两个步骤为例,Step1Step2

<template>
  <div>
    <keep - alive>
      <Step1 v - if="currentStep === 1"></Step1>
    </keep - alive>
    <Step2 v - if="currentStep === 2"></Step2>
    <button @click="prevStep" v - if="currentStep > 1">上一步</button>
    <button @click="nextStep">下一步</button>
  </div>
</template>

<script>
import Step1 from './Step1.vue';
import Step2 from './Step2.vue';

export default {
  components: {
    Step1,
    Step2
  },
  data() {
    return {
      currentStep: 1
    };
  },
  methods: {
    prevStep() {
      this.currentStep--;
    },
    nextStep() {
      this.currentStep++;
    }
  }
}
</script>

Step1.vue 组件中:

<template>
  <div>
    <label>用户名:</label>
    <input v - model="username">
    <label>密码:</label>
    <input type="password" v - model="password">
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    };
  }
}
</script>

当用户从 Step2 返回 Step1 时,由于 Step1keep - alive 缓存,usernamepassword 的值会保留,用户无需重新输入。

多标签页切换,保持状态

  1. 场景描述 类似于浏览器的多标签页功能,在一个应用中可能有多个标签页,每个标签页对应一个组件。例如,在一个项目管理应用中,可能有任务列表标签页、项目详情标签页、成员管理标签页等。用户在不同标签页之间切换,如果没有 keep - alive,每次切换标签页,组件都会重新渲染,之前的操作状态和数据都会丢失。使用 keep - alive 可以让每个标签页组件保持自己的状态,提升用户操作的连贯性。

  2. 代码示例 假设我们有三个组件 TaskListProjectDetailMemberManagement 分别对应任务列表、项目详情和成员管理标签页。

<template>
  <div>
    <ul>
      <li @click="activeTab = 'taskList'">任务列表</li>
      <li @click="activeTab = 'projectDetail'">项目详情</li>
      <li @click="activeTab ='memberManagement'">成员管理</li>
    </ul>
    <keep - alive>
      <TaskList v - if="activeTab === 'taskList'"></TaskList>
      <ProjectDetail v - if="activeTab === 'projectDetail'"></ProjectDetail>
      <MemberManagement v - if="activeTab ==='memberManagement'"></MemberManagement>
    </keep - alive>
  </div>
</template>

<script>
import TaskList from './TaskList.vue';
import ProjectDetail from './ProjectDetail.vue';
import MemberManagement from './MemberManagement.vue';

export default {
  components: {
    TaskList,
    ProjectDetail,
    MemberManagement
  },
  data() {
    return {
      activeTab: 'taskList'
    };
  }
}
</script>

TaskList.vue 组件中,假设我们有一个任务筛选功能:

<template>
  <div>
    <select v - model="filter">
      <option value="all">全部任务</option>
      <option value="completed">已完成任务</option>
      <option value="incomplete">未完成任务</option>
    </select>
    <ul>
      <li v - for="task in filteredTasks" :key="task.id">{{task.name}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      filter: 'all',
      tasks: [
        { id: 1, name: '任务1', completed: false },
        { id: 2, name: '任务2', completed: true }
      ]
    };
  },
  computed: {
    filteredTasks() {
      if (this.filter === 'all') {
        return this.tasks;
      } else if (this.filter === 'completed') {
        return this.tasks.filter(task => task.completed);
      } else {
        return this.tasks.filter(task =>!task.completed);
      }
    }
  }
}
</script>

当用户切换到其他标签页再切换回 TaskList 时,filter 的值会保持,用户看到的任务列表还是之前筛选后的状态。

提高复杂组件的渲染性能

  1. 场景描述 在一些复杂的可视化组件中,比如图表组件,渲染过程可能非常耗时,涉及到大量的数据计算和图形绘制。如果该组件在页面中频繁切换显示与隐藏,每次显示都重新渲染,会极大地消耗性能。使用 keep - alive 可以缓存该组件,避免重复渲染,提高应用的整体性能。

  2. 代码示例 以一个简单的柱状图组件 BarChart 为例,假设使用 chart.js 来绘制柱状图。

<template>
  <div>
    <canvas id="barChart"></canvas>
  </div>
</template>

<script>
import Chart from 'chart.js';

export default {
  data() {
    return {
      chartData: {
        labels: ['一月', '二月', '三月'],
        datasets: [
          {
            label: '销量',
            data: [100, 200, 150],
            backgroundColor: 'rgba(75, 192, 192, 0.2)',
            borderColor: 'rgba(75, 192, 192, 1)',
            borderWidth: 1
          }
        ]
      }
    };
  },
  mounted() {
    this.renderChart();
  },
  methods: {
    renderChart() {
      const ctx = document.getElementById('barChart').getContext('2d');
      new Chart(ctx, {
        type: 'bar',
        data: this.chartData,
        options: {
          scales: {
            yAxes: [
              {
                ticks: {
                  beginAtZero: true
                }
              }
            ]
          }
        }
      });
    }
  }
}
</script>

在父组件中,我们使用 keep - alive 包裹 BarChart 组件:

<template>
  <div>
    <keep - alive>
      <BarChart></BarChart>
    </keep - alive>
    <button @click="toggleChart">切换图表显示</button>
  </div>
</template>

<script>
import BarChart from './BarChart.vue';

export default {
  components: {
    BarChart
  },
  data() {
    return {
      showChart: true
    };
  },
  methods: {
    toggleChart() {
      this.showChart =!this.showChart;
    }
  }
}
</script>

当用户多次点击切换图表显示按钮时,由于 BarChartkeep - alive 缓存,不会重新调用 mounted 钩子重新绘制图表,提升了性能。

案例分析:电商 APP 中的商品详情与列表页

案例背景

我们开发一个电商 APP,用户可以在商品列表页浏览各种商品,点击商品进入商品详情页查看商品的详细信息,包括图片、价格、描述等。用户在浏览商品详情后,经常会返回商品列表页继续浏览其他商品。

未使用 Vue Keep - Alive 的问题

在没有使用 keep - alive 时,每次用户从商品详情页返回商品列表页,商品列表页都会重新渲染。这导致了以下问题:

  1. 性能问题:重新渲染商品列表页需要重新请求商品数据,尤其是当商品数据量较大时,网络请求时间较长,会造成页面卡顿,影响用户体验。
  2. 用户体验问题:用户在商品列表页可能已经进行了一些筛选操作,比如按照价格区间筛选、按照类别筛选等。重新渲染后,这些筛选条件会丢失,用户需要重新设置筛选条件,非常麻烦。

使用 Vue Keep - Alive 的解决方案

  1. 路由配置:在路由配置中,确保商品列表页组件和商品详情页组件的配置正确。
import Vue from 'vue';
import Router from 'vue - router';
import ProductList from '@/components/ProductList';
import ProductDetail from '@/components/ProductDetail';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/productList',
      name: 'ProductList',
      component: ProductList
    },
    {
      path: '/productDetail/:id',
      name: 'ProductDetail',
      component: ProductDetail
    }
  ]
});
  1. 在 App.vue 中使用 keep - alive:在 App.vue 中,使用 keep - alive 包裹商品列表页组件。
<template>
  <div id="app">
    <keep - alive>
      <router - view v - if="$route.name === 'ProductList'"></router - view>
    </keep - alive>
    <router - view v - if="$route.name === 'ProductDetail'"></router - view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
  1. 商品列表页组件优化:在商品列表页组件 ProductList 中,将数据请求放在 created 钩子中。同时,为了处理筛选条件的保存,我们可以在 data 中定义筛选条件变量,并在 watch 中监听这些变量的变化,进行相应的数据筛选操作。
export default {
  name: 'ProductList',
  data() {
    return {
      products: [],
      categoryFilter: '',
      priceMin: 0,
      priceMax: Infinity
    };
  },
  created() {
    this.fetchProducts();
  },
  watch: {
    categoryFilter() {
      this.filterProducts();
    },
    priceMin() {
      this.filterProducts();
    },
    priceMax() {
      this.filterProducts();
    }
  },
  methods: {
    fetchProducts() {
      // 模拟异步请求
      setTimeout(() => {
        this.products = [
          { id: 1, name: '商品1', category: '电子', price: 100 },
          { id: 2, name: '商品2', category: '服装', price: 200 }
        ];
        this.filterProducts();
      }, 1000);
    },
    filterProducts() {
      this.filteredProducts = this.products.filter(product => {
        return (
          (this.categoryFilter === '' || product.category === this.categoryFilter) &&
          product.price >= this.priceMin &&
          product.price <= this.priceMax
        );
      });
    }
  }
}
  1. 商品详情页组件:商品详情页组件 ProductDetail 可以根据商品的 id 从后台获取详细信息并展示。
export default {
  name: 'ProductDetail',
  data() {
    return {
      product: {}
    };
  },
  created() {
    const productId = this.$route.params.id;
    // 模拟根据 id 获取商品详情
    setTimeout(() => {
      this.product = { id: productId, name: '商品详情', description: '这是商品的详细描述' };
    }, 1000);
  }
}

通过以上步骤,当用户从商品详情页返回商品列表页时,商品列表页不会重新渲染,保留了之前的筛选状态,同时也提升了性能。

案例分析:复杂表单应用中的步骤切换

案例背景

开发一个复杂的多步骤表单应用,例如一个贷款申请表单,用户需要依次填写个人基本信息、财务状况、贷款用途等多个步骤的信息。每个步骤的表单都有一些必填项和复杂的校验逻辑。用户在填写过程中可能会在不同步骤之间来回切换。

未使用 Vue Keep - Alive 的问题

如果不使用 keep - alive,当用户从后面的步骤返回前面的步骤时,前面步骤填写的信息会丢失,用户需要重新填写,这对于用户来说是非常繁琐的操作,而且可能会导致用户因为厌烦而放弃填写表单。同时,每次切换步骤都重新渲染表单组件,也会消耗一定的性能。

使用 Vue Keep - Alive 的解决方案

  1. 组件结构:将每个表单步骤拆分成独立的组件,例如 Step1.vueStep2.vueStep3.vue 等。
  2. 父组件管理:在父组件中,使用 keep - alive 包裹每个步骤组件,并通过一个变量来控制当前显示的步骤。
<template>
  <div>
    <keep - alive>
      <Step1 v - if="currentStep === 1"></Step1>
      <Step2 v - if="currentStep === 2"></Step2>
      <Step3 v - if="currentStep === 3"></Step3>
    </keep - alive>
    <button @click="prevStep" v - if="currentStep > 1">上一步</button>
    <button @click="nextStep" v - if="currentStep < 3">下一步</button>
  </div>
</template>

<script>
import Step1 from './Step1.vue';
import Step2 from './Step2.vue';
import Step3 from './Step3.vue';

export default {
  components: {
    Step1,
    Step2,
    Step3
  },
  data() {
    return {
      currentStep: 1
    };
  },
  methods: {
    prevStep() {
      this.currentStep--;
    },
    nextStep() {
      if (this.currentStep === 1) {
        // 校验 Step1 的表单数据
        if (!this.$refs.step1.validate()) {
          return;
        }
      } else if (this.currentStep === 2) {
        // 校验 Step2 的表单数据
        if (!this.$refs.step2.validate()) {
          return;
        }
      }
      this.currentStep++;
    }
  }
}
</script>
  1. 步骤组件优化:在每个步骤组件中,处理好表单数据的绑定和校验逻辑。以 Step1.vue 为例:
<template>
  <div>
    <label>姓名:</label>
    <input v - model="name" ref="nameInput">
    <label>年龄:</label>
    <input v - model.number="age" ref="ageInput">
    <button @click="validate">校验</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      name: '',
      age: 0
    };
  },
  methods: {
    validate() {
      if (this.name === '') {
        alert('姓名不能为空');
        return false;
      }
      if (this.age <= 0) {
        alert('年龄必须大于 0');
        return false;
      }
      return true;
    }
  }
}
</script>

通过使用 keep - alive,当用户在不同步骤之间切换时,之前步骤填写的信息会保留,用户无需重新填写,提高了用户填写表单的效率和体验。

案例分析:多标签页项目管理应用

案例背景

开发一个项目管理应用,用户可以在不同的标签页之间切换,查看任务列表、项目进度、团队成员等信息。每个标签页对应的组件都有自己的交互逻辑和数据状态,例如任务列表标签页可以对任务进行排序、筛选,项目进度标签页可以查看项目的完成百分比等。

未使用 Vue Keep - Alive 的问题

如果不使用 keep - alive,每次切换标签页,组件都会重新渲染。这会导致用户在某个标签页进行的操作状态丢失,比如在任务列表标签页进行了排序和筛选操作,切换到其他标签页再切换回来,排序和筛选状态就消失了,用户需要重新操作,非常不便。同时,频繁的组件重新渲染也会消耗性能,降低应用的响应速度。

使用 Vue Keep - Alive 的解决方案

  1. 标签页组件结构:创建各个标签页对应的组件,如 TaskList.vueProjectProgress.vueTeamMembers.vue
  2. 父组件管理:在父组件中,使用 keep - alive 包裹每个标签页组件,并通过一个变量来控制当前激活的标签页。
<template>
  <div>
    <ul>
      <li @click="activeTab = 'taskList'">任务列表</li>
      <li @click="activeTab = 'projectProgress'">项目进度</li>
      <li @click="activeTab = 'teamMembers'">团队成员</li>
    </ul>
    <keep - alive>
      <TaskList v - if="activeTab === 'taskList'"></TaskList>
      <ProjectProgress v - if="activeTab === 'projectProgress'"></ProjectProgress>
      <TeamMembers v - if="activeTab === 'teamMembers'"></TeamMembers>
    </keep - alive>
  </div>
</template>

<script>
import TaskList from './TaskList.vue';
import ProjectProgress from './ProjectProgress.vue';
import TeamMembers from './TeamMembers.vue';

export default {
  components: {
    TaskList,
    ProjectProgress,
    TeamMembers
  },
  data() {
    return {
      activeTab: 'taskList'
    };
  }
}
</script>
  1. 任务列表组件优化:以 TaskList.vue 为例,假设我们有任务排序和筛选功能。
<template>
  <div>
    <select v - model="sortBy">
      <option value="name">按名称排序</option>
      <option value="dueDate">按截止日期排序</option>
    </select>
    <select v - model="filter">
      <option value="all">全部任务</option>
      <option value="completed">已完成任务</option>
      <option value="incomplete">未完成任务</option>
    </select>
    <ul>
      <li v - for="task in sortedAndFilteredTasks" :key="task.id">{{task.name}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tasks: [
        { id: 1, name: '任务1', dueDate: '2023 - 12 - 01', completed: false },
        { id: 2, name: '任务2', dueDate: '2023 - 12 - 02', completed: true }
      ],
      sortBy: 'name',
      filter: 'all'
    };
  },
  computed: {
    sortedAndFilteredTasks() {
      let sortedTasks = this.tasks.slice();
      if (this.sortBy === 'name') {
        sortedTasks.sort((a, b) => a.name.localeCompare(b.name));
      } else if (this.sortBy === 'dueDate') {
        sortedTasks.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
      }
      if (this.filter === 'completed') {
        sortedTasks = sortedTasks.filter(task => task.completed);
      } else if (this.filter === 'incomplete') {
        sortedTasks = sortedTasks.filter(task =>!task.completed);
      }
      return sortedTasks;
    }
  }
}
</script>

当用户在不同标签页之间切换时,由于 TaskList 等组件被 keep - alive 缓存,它们的状态(如排序和筛选条件)会保留,用户再次切换回该标签页时,能看到之前的操作状态,提升了用户体验和应用性能。

通过以上典型应用场景和案例分析,我们可以看到 Vue Keep - Alive 在提升应用性能和用户体验方面发挥着重要作用。在实际项目开发中,合理运用 keep - alive 可以有效地解决很多常见问题,提高项目的质量和用户满意度。同时,我们也需要注意在使用 keep - alive 时,对组件的生命周期钩子函数的正确使用,以及对组件状态管理的合理性,以确保应用的稳定运行。