vue项目中实现缓存的最佳方案详解


Posted in Javascript onJuly 11, 2019

需求

在开发vue的项目中有遇到了这样一个需求:一个视频列表页面,展示视频名称和是否收藏,点击进去某一项观看,可以收藏或者取消收藏,返回的时候需要记住列表页面的页码等状态,同时这条视频的收藏状态也需要更新, 但是从其他页面进来视频列表页面的时候不缓存这个页面,也就是进入的时候是视频列表页面的第一页

vue项目中实现缓存的最佳方案详解

一句话总结一下: pageAList->pageADetail->pageAList, 缓存pageAList, 同时该视频的收藏状态如果发生变化需要更新, 其他页面->pageAList, pageAList不缓存

在网上找了很多别人的方法,都不满足我们的需求

然后我们团队几个人捣鼓了几天,还真的整出了一套方法,实现了这个需求

实现后的效果

无图无真相,用一张gif图来看一下实现后的效果吧!!!

操作流程:

vue项目中实现缓存的最佳方案详解

  • 首页->pageAList, 跳转第二页 ->首页-> pageAList,页码显示第一页,说明从其他页面进入pageAList, pageAList页面没有被缓存
  • pageAList, 跳转到第三页,点击视频22 -> 进入视频详情页pageADetail,点击收藏,收藏成功,点击返回 -> pageAList显示的是第三页,并且视频22的收藏状态从未收藏变成已收藏,说明从pageADetail进入pageAList,pageAList页面缓存了,并且更新了状态

说明:

  • 二级缓存: 也就是从A->B->A,缓存A
  • 三级缓存:A->B->C->B->A, 缓存A,B  因为项目里面绝大部分是二级缓存,这里我们就做二级缓存,但是这不代表我的这个缓存方法不适用三级缓存,三级缓存后面我也会讲如何实现

实现二级缓存

用vue-cli2的脚手架搭建了一个项目,用这个项目来说明如何实现

先来看看项目目录

vue项目中实现缓存的最佳方案详解

删除了无用的components目录和assets目录,新增了src/pages目录和src/store目录, pages页面用来存放页面组件, store不多说,存放vuex相关的东西,新增了server/app.js目录,用来启动后台服务

1. 前提条件

  • 项目引入vue,vuex, vue-router,axios等vue全家桶
  • 引入element-ui,只是为了项目美观,毕竟本人懒癌晚期,不想自己写样式
  • 在config/index.js里面配置前端代理

vue项目中实现缓存的最佳方案详解

  • 引入express,启动后台,后端开3003端口,给前端提供api支持  来看看服务端代码server/app.js,非常简单,就是造了30条数据,写了3个接口,几十行文件直接搭建了一个node服务器,简单粗暴解决数据模拟问题,会mock用mock也行
const express = require('express')
// const bodyParser = require('body-parser')
const app = express()
let allList = Array.from({length: 30}, (v, i) => ({
 id: i,
 name: '视频' + i,
 isCollect: false
}))
// 后台设置允许跨域访问
// 前后端都是本地localhost,所以不需要设置cors跨域,如果是部署在服务器上,则需要设置
// app.all('*', function (req, res, next) {
// res.header('Access-Control-Allow-Origin', '*')
// res.header('Access-Control-Allow-Headers', 'X-Requested-With')
// res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
// res.header('X-Powered-By', ' 3.2.1')
// res.header('Content-Type', 'application/json;charset=utf-8')
// next()
// })
app.use(express.json())
app.use(express.urlencoded({extended: false}))
// 1 获取所有的视频列表
app.get('/api/getVideoList', function (req, res) {
 let query = req.query
 let currentPage = query.currentPage
 let pageSize = query.pageSize
 let list = allList.slice((currentPage - 1) * pageSize, currentPage * pageSize)
 res.json({
 code: 0,
 data: {
  list,
  total: allList.length
 }
 })
})
// 2 获取某一条视频详情
app.get('/api/getVideoDetail/:id', function (req, res) {
 let id = Number(req.params.id)
 let info = allList.find(v => v.id === id)
 res.json({
 code: 0,
 data: info
 })
})
// 3 收藏或者取消收藏视频
app.post('/api/collectVideo', function (req, res) {
 let id = Number(req.body.id)
 let isCollect = req.body.isCollect
 allList = allList.map((v, i) => {
 return v.id === id ? {...v, isCollect} : v
 })
 res.json({code: 0})
})
const PORT = 3003
app.listen(PORT, function () {
 console.log('app is listening port' + PORT)
})

2. 路由配置

在路由配置里面把需要缓存的路由的meta添加keepAlive属性,值为true, 这个想必大家都知道,是缓存路由组件的
在我们项目里面,需要缓存的路由是pageAList,所以这个路由的meta的keepAlive设置成true,其他路由正常写,路由文件src/router/index.js如下:

import Vue from 'vue'
import Router from 'vue-router'
import home from '../pages/home'
import pageAList from '../pages/pageAList'
import pageADetail from '../pages/pageADetail'
import pageB from '../pages/pageB'
import main from '../pages/main'
Vue.use(Router)

export default new Router({
 routes: [
 {
  path: '/',
  name: 'main',
  component: main,
  redirect: '/home',
  children: [
  {
   path: 'home',
   name: 'home',
   component: home
  },
  {
   path: 'pageAList',
   name: 'pageAList',
   component: pageAList,
   meta: {
   keepAlive: true
   }
  },
  {
   path: 'pageB',
   component: pageB
  }
  ]
 },
 {
  path: '/pageADetail',
  name: 'pageADetail',
  component: pageADetail
 }
 ]
})

3. vuex配置

vuex的store.js里面存储一个名为excludeComponents的数组,这个数组用来操作需要做缓存的组件

state.js

const state = {
 excludeComponents: [] 
}
export default state

同时在mutations.js里面加入两个方法, addExcludeComponent是往excludeComponents里面添加元素的,removeExcludeComponent是往excludeComponents数组里面移除元素

注意: 这两个方法的第二个参数是数组或者组件name

mutations.js

const mutations = {
 addExcludeComponent (state, excludeComponent) {
 let excludeComponents = state.excludeComponents
 if (Array.isArray(excludeComponent)) {
  state.excludeComponents = [...new Set([...excludeComponents, ...excludeComponent])]
 } else {
  state.excludeComponents = [...new Set([...excludeComponents, excludeComponent])]
 }
 },
 // excludeComponent可能是组件name字符串或者数组
 removeExcludeComponent (state, excludeComponent) {
 let excludeComponents = state.excludeComponents
 if (Array.isArray(excludeComponent)) {
  for (let i = 0; i < excludeComponent.length; i++) {
  let index = excludeComponents.findIndex(v => v === excludeComponent[i])
  if (index > -1) {
   excludeComponents.splice(index, 1)
  }
  }
 } else {
  for (let i = 0, len = excludeComponents.length; i < len; i++) {
  if (excludeComponents[i] === excludeComponent) {
   excludeComponents.splice(i, 1)
   break
  }
  }
 }
 state.excludeComponents = excludeComponents
 }
}
export default mutations

4. keep-alive包裹router-view

将App.vue的router-view用keep-alive组件包裹, main.vue的路由也需要这么包裹,这点非常重要,因为pageAList组件是从它们的router-view中匹配的

<keep-alive :exclude="excludeComponents"><som-component></some-component></keep-alive>这个写法大家应该不会陌生,这也是尤大神官方推荐的缓存方法, exclude属性值可以是组件名称字符串(组件选项的name属性)或者数组,代表不缓存这些组件,所以vuex里面的addExcludeComponent是代表要缓存组件,addExcludeComponent代表不缓存组件,这里稍微有点绕,请牢记这个规则,这样接下来你就不会被绕进去了。

App.vue

<template>
 <div id="app">
 <keep-alive :exclude="excludeComponents">
  <router-view v-if="$route.meta.keepAlive"></router-view>
 </keep-alive>
 <router-view v-if="!$route.meta.keepAlive"></router-view>
 </div>
</template>

<script>
export default {
 name: 'App',
 computed: {
 excludeComponents () {
  return this.$store.state.excludeComponents
 }
 }
}
</script

main.vue

<template>
 <div>
 <ul>
  <li v-for="nav in navs" :key="nav.name">
  <router-link :to="nav.name">{{nav.title}}</router-link>
  </li>
 </ul>
 <keep-alive :exclude="excludeComponents">
  <router-view v-if="$route.meta.keepAlive"></router-view>
 </keep-alive>
 <router-view v-if="!$route.meta.keepAlive"></router-view>
 </div>
</template>
<script>
export default {
 name: 'main.vue',
 data () {
 return {
  navs: [{
  name: 'home',
  title: '首页'
  }, {
  name: 'pageAList',
  title: 'pageAList'
  }, {
  name: 'pageB',
  title: 'pageB'
  }]
 }
 },
 methods: {
 },
 computed: {
 excludeComponents () {
  return this.$store.state.excludeComponents
 }
 },
 created () {
 }
}
</script>

接下来的两点设置非常重要

5. 一级组件

对于需要缓存的一级路由pageAList,添加两个路由生命周期钩子beforeRouteEnter和beforeRouteLeave

import {getVideoList} from '../api'
export default {
 name: 'pageAList', // 组件名称,和组件对应的路由名称不需要相同
 data () {
 return {
  currentPage: 1,
  pageSize: 10,
  total: 0,
  allList: [],
  list: []
 }
 },
 methods: {
 getVideoList () {
  let params = {currentPage: this.currentPage, pageSize: this.pageSize}
  getVideoList(params).then(r => {
  if (r.code === 0) {
   this.list = r.data.list
   this.total = r.data.total
  }
  })
 },
 goIntoVideo (item) {
  this.$router.push({name: 'pageADetail', query: {id: item.id}})
 },
 handleCurrentPage (val) {
  this.currentPage = val
  this.getVideoList()
 }
 },
 beforeRouteEnter (to, from, next) {
 next(vm => {
  vm.$store.commit('removeExcludeComponent', 'pageAList')
  next()
 })
 },
 beforeRouteLeave (to, from, next) {
 let reg = /pageADetail/
 if (reg.test(to.name)) {
  this.$store.commit('removeExcludeComponent', 'pageAList')
 } else {
  this.$store.commit('addExcludeComponent', 'pageAList')
 }
 next()
 },
 activated () {
 this.getVideoList()
 },
 mounted () {
 this.getVideoList()
 }
}
  • beforeRouteEnter,进入这个组件pageAList之前,在excludeComponents移除当前组件,也就是缓存当前组件,所以任何路由跳转到这个组件,这个组件其实都是被缓存的,都会触发activated钩子
  • beforeRouteLeave: 离开当前页面,如果跳转到pageADetail,那么就需要在excludeComponents移除当前组件pageAList,也就是缓存当前组件,如果是跳转到其他页面,就需要把pageAList添加进去excludeComponents,也就是不缓存当前组件
  • 获取数据的方法getVideoList在mounted或者created钩子里面调取,如果二级路由更改数据,一级路由需要更新,那么就需要在activated钩子里再获取一次数据,我们这个详情可以收藏,改变列表的状态,所以这两个钩子都使用了

6. 二级组件

对于需要缓存的一级路由的二级路由组件pageADetail,添加beforeRouteLeave路由生命周期钩子

在这个beforeRouteLeave钩子里面,需要先清除一级组件的缓存状态,如果跳转路由匹配到一级组件,再缓存一级组件

beforeRouteLeave (to, from, next) {
 let componentName = ''
 // 离开详情页时,将pageAList添加到exludeComponents里,也就是将需要缓存的页面pageAList置为不缓存状态
 let list = ['pageAList']
 this.$store.commit('addExcludeComponent', list)
 // 缓存组件路由名称到组件name的映射
 let map = new Map([['pageAList', 'pageAList']])
 componentName = map.get(to.name) || ''
 // 如果离开的时候跳转的路由是pageAList,将pageAList从exludeComponents里面移除,也就是要缓存pageAList
 this.$store.commit('removeExcludeComponent', componentName)
 next()
 }

7.实现方法总结

进入了pageAList,就在beforeRouteEnter里缓存了它,离开当前组件的时候有两种情况:

1 跳转进去pageADetail,在pageAList的beforeRouteLeave钩子里面缓存pageAList,从pageADetail离开的时候,也有两种情况

(1) 回到pageAList,那么在pageADetail的beforeRouteLeave钩子里面缓存了pageAList,所以这就是从pageAList-pageADetail-pageAList的时候,pageAList可以被缓存,还是之前的页码状态

(2) 进入其他路由,在pageADetail的beforeRouteLeave钩子里面清除了pageAList的缓存

2 跳转到非pageADetail的页面,在pageAList的beforeRouteLeave钩子里面清除pageAList的缓存

方案评估

自认为用这个方案来实现缓存,最终的效果非常完美了

缺点:

  1. 代码有点多,缓存代码不好复用
  2. 性能问题:如果在要缓存的一级组件里面写了activated钩子,那么从非一级组件对应的二级组件进入到要缓存的一级组件的时候,会发送两次接口请求数据,mounted里面一次, activated里面一次, 所以如果想追求几行代码完美解决缓存问题的,这里就有点无能为力了

项目源码

项目源码的github地址 (本地下载),欢迎大家克隆下载

项目启动与效果演示

  • npm install安装项目依赖
  • npm run server启动后台服务器监听本地3003端口
  • npm run dev启动前端项目

三级缓存

上面的方法二级缓存就够了

上面我们说的是两个页面,二级缓存的问题,现在假设有三个页面,A1-A2-A3,一步步点进去,要求从A3返回到A2的时候,缓存A2,再从A2返回A1的时候,缓存A1,大家可以自己动手研究下,这里就不写了,其实就是上面的思路,留给大家研究。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript中的new的使用方法与注意事项
May 16 Javascript
JS的反射问题
Apr 07 Javascript
两个Javascript小tip资料
Nov 23 Javascript
情人节之礼 js项链效果
Feb 13 Javascript
datagrid框架的删除添加与修改
Apr 08 Javascript
简介JavaScript中toUpperCase()方法的使用
Jun 06 Javascript
JavaScript弹出对话框的三种方式
Mar 23 Javascript
js中的面向对象入门
Mar 06 Javascript
深入理解 JavaScript 中的 JSON
Apr 06 Javascript
w3c编程挑战_初级脚本算法实战篇
Jun 23 Javascript
Angular5给组件本身的标签添加样式class的方法
Apr 07 Javascript
vue打包时去掉所有的console.log
Apr 10 Vue.js
浅入深出Vue之组件使用
Jul 11 #Javascript
elementUI 动态生成几行几列的方法示例
Jul 11 #Javascript
JS为什么说async/await是generator的语法糖详解
Jul 11 #Javascript
Vue 使用计时器实现跑马灯效果的实例代码
Jul 11 #Javascript
Vue 子组件与数据传递问题及注意事项
Jul 11 #Javascript
ES6的异步终极解决方案分享
Jul 11 #Javascript
微信小程序的开发范式BeautyWe.js入门详解
Jul 10 #Javascript
You might like
PHP 强制性文件下载功能的函数代码(任意文件格式)
2010/05/26 PHP
实战mysql导出中文乱码及phpmyadmin导入中文乱码的解决方法
2010/06/11 PHP
3个PHP多维数组转为一维数组的方法实例
2014/03/13 PHP
php连接oracle数据库的方法(测试成功)
2016/05/26 PHP
php实现用户登陆简单实例
2017/04/04 PHP
PHP实现图片防盗链破解操作示例【解决图片防盗链问题/反向代理】
2020/05/29 PHP
NodeJS学习笔记之Connect中间件模块(二)
2015/01/27 NodeJs
javascript正则表达式基础知识入门
2015/04/20 Javascript
jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween
2015/12/03 Javascript
怎么限制input的text里输入的值只能是数字(正则、js)
2016/05/16 Javascript
Bootstrap3多级下拉菜单
2017/02/24 Javascript
浅谈Vuex@2.3.0 中的 state 支持函数申明
2017/11/22 Javascript
JS实现网页抢购功能(触发,终止脚本)
2017/11/27 Javascript
AngularJS $http post 传递参数数据的方法
2018/10/09 Javascript
浅谈HTTP 缓存的那些事儿
2018/10/17 Javascript
详解JavaScript中的坐标和距离
2019/05/27 Javascript
js计算最大公约数和最小公倍数代码实例
2019/09/11 Javascript
countup.js实现数字动态叠加效果
2019/10/17 Javascript
在vue中通过render函数给子组件设置ref操作
2020/11/17 Vue.js
pycharm远程开发项目的实现步骤
2019/01/20 Python
python常用库之NumPy和sklearn入门
2019/07/11 Python
利用pandas合并多个excel的方法示例
2019/10/10 Python
python模拟实现分发扑克牌
2020/04/22 Python
opencv 图像礼帽和图像黑帽的实现
2020/07/07 Python
Tarte Cosmetics官网:美国最受欢迎的化妆品公司之一
2017/08/24 全球购物
Desigual英国官网:在线购买原创服装
2018/03/09 全球购物
好莱坞百老汇御用王牌美妆:Koh Gen Do 江原道
2018/04/03 全球购物
阿迪达斯越南官网:adidas越南
2020/07/19 全球购物
英语师范专业毕业生自荐信
2013/09/21 职场文书
酒店副总岗位职责
2013/12/24 职场文书
小学教师师德感言
2014/02/10 职场文书
大型活动组织方案
2014/05/10 职场文书
生日宴会策划方案
2014/06/03 职场文书
工作疏忽、懈怠的检讨书
2014/09/11 职场文书
left join、inner join、right join的区别
2021/04/05 MySQL
Vue如何实现组件间通信
2021/05/15 Vue.js