广告
返回顶部
首页 > 资讯 > 前端开发 > html >如何解决vue spa应用中的路由缓存问题
  • 472
分享到

如何解决vue spa应用中的路由缓存问题

2024-04-02 19:04:59 472人浏览 薄情痞子
摘要

这篇文章将为大家详细讲解有关如何解决Vue spa应用中的路由缓存问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。单页面应用中的路由缓存问题通常我们在进行页面前后退时,

这篇文章将为大家详细讲解有关如何解决Vue spa应用中的路由缓存问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

单页面应用中的路由缓存问题

通常我们在进行页面前后退时,浏览器通常会帮我们记录下之前滚动的位置,这使得我们不会在每次后退的时候都丢失之前的浏览器记录定位。但是在现在愈发流行的SPA(single page application 单页面应用)中,当我们从父级页面打开子级页面,或者从列表页面进入详情页面,此时如果回退页面,会发现之前我们浏览的滚动记录没有了,页面被置顶到了最顶部,仿佛是第一次进入这个页面一样。这是因为在spa页面中的url与路由容器页面所对应,当页面路径与其发生不匹配时,该页面组件就会被卸载,再次进入页面时,整个组件的生命周期就会完全重新走一遍,包括一些数据的请求与渲染,所以之前的滚动位置和渲染的数据内容也都完全被重置了。

vue中的解决方式

vue.js最贴心的一点就是提供了非常多便捷的api,为开发者考虑到很多的应用场景。在vue中,如果想缓存路由,我们可以直接使用内置的keep-alive组件,当keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

内置组件keep alive

keep-alive是Vue.js的一个内置组件。它主要用于保留组件状态或避免重新渲染。

使用方法如下:

<keep-alive :include="['a', 'b']">
 <component :is="view"></component>
</keep-alive>

keep-alive组件会去匹配name名称为 'a', 'b' 的子组件,在匹配到以后会帮助组件缓存优化该项组件,以达到组件不会被销毁的目的。

实现原理

先简要看下keep-alive组件内部实现代码,具体代码可以见Vue GitHub

created () {
 this.cache = Object.create(null)
 this.keys = []
}

在created生命周期中会用Object.create方法创建一个cache对象,用来作为缓存容器,保存vnode节点。Tip: Object.create(null)创建的对象没有原型链更加纯净

render () {
 const slot = this.$slots.default
 const vnode: VNode = getFirstComponentChild(slot)
 const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
 if (componentOptions) {
  // check pattern 检查匹配是否为缓存组件,主要根据include传入的name来对应
  const name: ?string = getComponentName(componentOptions)
  const { include, exclude } = this
  if (
   // not included  该判断中判断不被匹配,则直接返回当前的vnode(虚拟dom)
  (include && (!name || !matches(include, name))) ||
  // excluded
  (exclude && name && matches(exclude, name))
  ) {
   return vnode
  }

  const { cache, keys } = this
  const key: ?string = vnode.key == null
   // same constructor may get reGIStered as different local components
   // so cid alone is not enough (#3269)
   ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
   : vnode.key
  if (cache[key]) {
   //查看cache对象中已经缓存了该组件,则vnode直接使用缓存中的组件实例
   vnode.componentInstance = cache[key].componentInstance
   // make current key freshest 
   remove(keys, key)
   keys.push(key)
  } else {
   //未缓存的则缓存实例
   cache[key] = vnode
   keys.push(key)
   // prune oldest entry
   if (this.max && keys.length > parseInt(this.max)) {
    pruneCacheEntry(cache, keys[0], keys, this._vnode)
   }
  }

  vnode.data.keepAlive = true
 }
 return vnode || (slot && slot[0])
}

上述代码主要是在render函数中对是否是缓存渲染进行判断

vue keep-alive内部实现的基本流程就是:

  1. 首先通过getFirstComponentChild获取到内部的子组件

  2. 然后拿到该组件的name与keep-alive组件上定义的include与exclude属性进行匹配,

  3. 如果不匹配就表示不缓存组件,就直接返回该组件的vnode(vnode就是一个虚拟的dom树结构,由于原生dom上的属性非常多,消耗巨大,使用这种模拟方式会减少很多dom操作的开销)

  4. 如果匹配到,则在cache对象中查看是否已经缓存过该实例,如果有就直接将缓存的vnode的componentInstance(组件实例)覆盖到目前的vnode上面,否则将vnode存储在cache中。

React中的解决方案

在react中没有提供类似于vue的keep-alive的解决方案,这意味这我们可能需要自己编写一些代码或者通过一些第三方的模块来解决。

在React项目gitHub的该issue中进行了相关讨论,开发维护人员给出了两种方式来解决:

  • 将数据与组件分开缓存。例如,你可以将state提升到一个不会被卸载的父级组件,或者像redux一样将其放在一个侧面缓存中。我们也正在为此开发一类的API支持(context)。

  • 不要去卸载你要“保持活动”的视图,只需使用style={{display:'none'}}属性去隐藏它们。

如何解决vue spa应用中的路由缓存问题

1. 集中的状态管理恢复快照方式

在React中通过redux或mobx集中的状态管理来缓存页面数据以及滚动条等信息,以达到缓存页面的效果。

componentDidMount() {
 const {app: {dataSoruce = [], scrollTop}, loadData} = this.props;
 if (dataSoruce.length) { //判断redux中是否已经有数据源
  // 有数据则不再加载收据,只恢复滚动状态
  window.scrollTo(0, scrollTop);
 } else { //没有数据就去请求数据源
  this.props.loadData(); // 在redux中定义的数据请求的action
 }
}

handleClik = () => {
 在点击进入下一级页面前先保存当前的滚动距离
 const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
 const {saveScrollTop} = this.props;
 saveScrollTop(scrollTop);
}

首先我们可以在redux中为页面定义异步的action,将请求回来的数据放入集中的store中(redux的该相关具体用法不在细述)。在sotre里我们可以保存当前页面的数据源、滚动条高度以及其他一些可能要用到的分页数据等来帮助我们恢复状态。

在componentDidMount生命周期里,首先根据redux里store中的对应的字段,判断是否已经加载过数据源。如果已经缓存过数据则不再去请求数据源,只去恢复一下store里的存储过的一些滚动条位置信息等。如果还未请求过数据,就使用在redux中定义的异步action去请求数据,在将数据在reducer里将数据存到store中。 在render函数里,我们只需要读取redux里存储的数据即可。

为了保留要缓存页面的一些状态信息,如滚动条、分页、操作状态,我们可以在进行对应操作时候将这些信息存入redux的store中,这样当我们恢复页面时,就可以将这些对应状态一一读取并还原。

2. 使用display的属性来切换显示隐藏路由组件

想要display的属性来切换显示隐藏路由组件,首先要保证路由组件不会在url变化时候被卸载。在react-router中最使用的Route组件,它可以通过我们定义的path属性来与页面路径来进行匹配,并渲染对应的组件,从而达到保持UI与URL同步变化的效果。

首先简要看下Route组件的实现 GitHub Route.js

return (
 <RouterContext.Provider value={props}>
  {children && !isEmptyChildren(children)
   ? children
   : props.match // props.match 属性来确定是否要渲染组件
    ? component
     ? React.createElement(component, props)
     : render
      ? render(props)
      : null
    : null}
 </RouterContext.Provider>
);

上述代码出现在关键的render方法最后的return中

Route组件会根据props对象中的match属性来确定是否要渲染组件,如果match匹配到了就使用Route组件上传递的component或者render属性来渲染对应组件,否则就返回null。

然后溯源而上,我们找到了props对象中关于match的定义:

const location = this.props.location || context.location;
const match = this.props.computedMatch
 ? this.props.computedMatch // <Switch> already computed the match for us
 : this.props.path
  ? matchPath(location.pathname, this.props)
  : context.match;

const props = { ...context, location, match };

上述代码显示,match首先会从组件的this.props中的computedMatch属性来判断:如果this.props中存在computedMatch则直接使用定义好的computedMatch属性赋值给match,否则如果this.props.path存在,就会使用matchPath方法来根据当前的location.pathname来判断是否匹配。

然而在react router的Route组件API文档中我们似乎没有看到过有关于computedMatch的介绍,不过在源码中有一行这样的注释

// <Switch> already computed the match for us

该注释说在<Switch>组件中已经为我们计算了该匹配。

接下来我们再去了解一下Switch组件:

Switch组件只会渲染第一个被location匹配到的并且作为子元素的<Route>或者<Redirect>

我们翻开Switch组件的实现源码:

let element, match; // 定义最后返回的组件元素,和match匹配变量
 
 React.Children.forEach(this.props.children, child => {
  if (match == null && React.isValidElement(child)) { // 如果match没有内容则进入该判断
   element = child;
 
   const path = child.props.path || child.props.from;
 
   match = path // 该三元表达式只有在匹配到后会给match赋值一个对象,否则match一直为null
    ? matchPath(location.pathname, { ...child.props, path })
    : context.match;
  }
 });
 
 return match
  ? React.cloneElement(element, { location, computedMatch: match })
  : null;

首先我们找到computedMatch属性是在React.cloneElement方法中,cloneElement方法会将追加定义的属性合并到该clone组件元素上,并返回clone后的React组件,等于就是将新的props属性传入组件并返回新组件。

在上文中找到computedMatch的值match也是根据matchPath来判断是否匹配的,matchPath是react router中的一个API,该方法会根据你传入的第一个参数pathname与第二个要匹配的props属性参数来判断是否匹配。如果匹配就返一个对象类型并包含相关的属性,否则返回null。

在React.Children.forEach循环子元素的方法中,matchPath方法判断当前pathname是否匹配,如果匹配就给定义的match变量进行赋值,所以当match被赋值以后,后续的循环就也不会再进行匹配赋值,因为Switch组件只会渲染第一次与之匹配的组件。

3. 实现一个路由缓存组件

我们知道Switch组件只会渲染第一项匹配的子组件,如果可以将匹配到的组件都渲染出来,然后只用display的block和none来切换是否显示,这也就实现了第二种解决方案。

参照Switch组件来封装一个RouteCache组件:

import React from 'react';
import PropTypes from 'prop-types';
import {matchPath} from 'react-router';
import {Route} from 'react-router-dom';

class RouteCache extends React.Component {

 static propTypes = {
  include: PropTypes.oneOfType([
   PropTypes.bool,
   PropTypes.array
  ])
 };

 cache = {}; //缓存已加载过的组件

 render() {
  const {children, include = []} = this.props;

  return React.Children.map(children, child => {
   if (React.isValidElement(child)) { // 验证是否为是react element
    const {path} = child.props;
    const match = matchPath(location.pathname, {...child.props, path});

    if (match && (include === true || include.includes(path))) {
     //如果匹配,则将对应path的computedMatch属性加入cache对象里
     //当include为true时,缓存全部组件,当include为数组时缓存对应组件
     this.cache[path] = {computedMatch: match};
    }

    //可以在computedMatch里追加入一个display属性,可以在路由组件的props.match拿到
    const cloneProps = this.cache[path] && Object.assign(this.cache[path].computedMatch, {display: match ? 'block' : 'none'});

    return <div style={{display: match ? 'block' : 'none'}}>{React.cloneElement(child, {computedMatch: cloneProps})}</div>;
   }

   return null;
  });
 }
}

// 使用
<RouteCache include={['/login', '/home']}>
 <Route path="/login" component={Login} />
 <Route path="/home" component={App} />
</RouteCache>

在阅读了源码后,我们知道Route组件会根据它的this.props.computedMatch来判断是否要渲染该组件。

我们在组件内部创建一个cache对象,将已经匹配到的组件的computedMatch属性写入该缓存对象中。这样即使当url不再匹配时,也能通过读取cache对象中该路径的值,并使用React .cloneElement方法将computedMatch属性赋值给组件的props。这样已缓存过的路由组件就会被一直渲染出来,组件就不会被卸载掉。

因为组件内部可能会包裹多个路由组件,所以使用React.Children.map方法将内部包含的子组件都循环返回。

为了UI与路由对应显示正确,我们通过当前的计算得出的match属性,来隐藏掉不匹配的组件,只为我们展示匹配的组件即可。如果你不想在组件外再套一层div,也可以在组件内部通过this.props.match中的display属性来切换显示组件。

仿照vue keep alive的形式,设置一个 include 参数API。当参数为true时缓存内部的所有子组件,当参数为数组时则缓存对应的path路径组件。

使用效果

如何解决vue spa应用中的路由缓存问题

在最初时,从未被url匹配过的组件不会被渲染,里面的dom结构是空的。

如何解决vue spa应用中的路由缓存问题

当切换到对应组件时,当前的组件被渲染,而之前已匹配的组件不会被卸载,只是被隐藏

如何解决vue spa应用中的路由缓存问题

在输出日志中可以看到,当我们不停的来回切换时,componentDidMount生命周期也只执行一次,在props.match中我们可以获取到当前的display值。

4. 另外的也可以采用一些第三方组件模块来实习缓存机制:

react-keeper
react-router-cache-route
react-live-route

关于“如何解决vue spa应用中的路由缓存问题”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

--结束END--

本文标题: 如何解决vue spa应用中的路由缓存问题

本文链接: https://www.lsjlt.com/news/74012.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何解决vue spa应用中的路由缓存问题
    这篇文章将为大家详细讲解有关如何解决vue spa应用中的路由缓存问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。单页面应用中的路由缓存问题通常我们在进行页面前后退时,...
    99+
    2022-10-19
  • Vue SPA首屏加载缓慢问题如何解决
    本文小编为大家详细介绍“Vue SPA首屏加载缓慢问题如何解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“Vue SPA首屏加载缓慢问题如何解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧...
    99+
    2023-07-05
  • 如何解决vue中路由映射的问题
    小编给大家分享一下如何解决vue中路由映射的问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!在项目中遇到了一个问题,明明在Ro...
    99+
    2022-10-19
  • 如何解决vue中keep-alive缓存问题
    这篇文章将为大家详细讲解有关如何解决vue中keep-alive缓存问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。vue开发的时候,我们经常会有这样的需求:开发一个详...
    99+
    2022-10-19
  • 如何解决vue组件路由高亮的问题
    这篇文章给大家分享的是有关如何解决vue组件路由高亮的问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。vue是什么Vue是一套用于构建用户界面的渐进式JavaScript框架,Vue与其它大型框架的区别是,使用...
    99+
    2023-06-15
  • Vue路由history模式如何解决404问题
    小编给大家分享一下Vue路由history模式如何解决404问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!问题背景:vue-...
    99+
    2022-10-19
  • 如何解决vue页面缓存问题
    这篇文章给大家分享的是有关如何解决vue页面缓存问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。比如有一个列表页面,然后列表每项都有一个详情,之前用vue1.x的时候,页面缓存...
    99+
    2022-10-19
  • 如何解决vue单页缓存存在的问题
    这篇文章主要介绍了如何解决vue单页缓存存在的问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1.css同名覆盖,解决方法:父组件加上sc...
    99+
    2022-10-19
  • 如何解决vue-cli 默认路由再子路由选中下的选中状态问题
    小编给大家分享一下如何解决vue-cli 默认路由再子路由选中下的选中状态问题,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!vue-cli是Vue.js官方脚手架命令行工具,我们可以用它快...
    99+
    2022-10-19
  • 如何解决vue路由守卫及路由守卫无限循环问题
    这篇文章主要介绍了如何解决vue路由守卫及路由守卫无限循环问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。先贴一波官方文档的内容const...
    99+
    2022-10-19
  • AJAX中如何解决缓存问题
    这篇文章将为大家详细讲解有关AJAX中如何解决缓存问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。做一个项目用到Ajax,开始觉得挺好,后来发现一个问题,例如删除一项,...
    99+
    2022-10-19
  • 如何解决vue-router路由参数刷新消失的问题
    这篇文章主要为大家展示了“如何解决vue-router路由参数刷新消失的问题”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何解决vue-router路由参数刷...
    99+
    2022-10-19
  • Vue入口文件index.html缓存问题如何解决
    这篇文章主要介绍“Vue入口文件index.html缓存问题如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue入口文件index.html缓存问题如何解决”文章能帮助大家解决问题。Vue入...
    99+
    2023-07-05
  • 如何解决Vue相同路由不同参数的刷新问题
    这篇文章给大家分享的是有关如何解决Vue相同路由不同参数的刷新问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。在使用vue和vue-router开发spa应用时,我们会遇到这样...
    99+
    2022-10-19
  • 如何解决Vue路由this.route.push跳转页面不刷新的问题
    这篇文章主要讲解了“如何解决Vue路由this.route.push跳转页面不刷新的问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何解决Vue路由this.route.push跳转页面...
    99+
    2023-06-20
  • 如何解决vue路由变化页面数据不刷新的问题
    这篇文章给大家分享的是有关如何解决vue路由变化页面数据不刷新的问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。每天记录一点点,把我遇到的问题记录下来, 希望可以帮助到更多和我...
    99+
    2022-10-19
  • 如何解决vue router动态路由下让每个子路由都是独立组件的问题
    这篇文章主要介绍如何解决vue router动态路由下让每个子路由都是独立组件的问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!vue-router 之动态路由vue-router...
    99+
    2022-10-19
  • C#路径问题中的如何保存问题的解决方法
    本篇内容主要讲解“C#路径问题中的如何保存问题的解决方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#路径问题中的如何保存问题的解决方法”吧!C#路径问题之保存路径的提问我在项目里建立了一个...
    99+
    2023-06-18
  • 如何解决Redis缓存异常的问题
    这篇文章将为大家详细讲解有关如何解决Redis缓存异常的问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。缓存雪崩缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都...
    99+
    2022-10-19
  • Java项目中的NPM缓存问题:如何解决?
    在现代Web开发中,NPM(Node Package Manager)已经成为了不可或缺的一部分。NPM是一个用于Node.js的包管理器,可以帮助开发者轻松地安装、更新和管理项目所需的各种依赖包。然而,在Java项目中使用NPM时,我们...
    99+
    2023-08-30
    leetcode npm 缓存
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作