手机版

vue spa应用中的路由缓存问题及解决方案

时间:2021-08-23 来源:互联网 编辑:宝哥软件园 浏览:

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

通常我们在页面前面后退时,浏览器通常会帮助我们记录之前的滚动位置,这使得我们每次后退时都不会丢失之前的浏览器记录定位。但是,在越来越流行的SPA(单页应用)中,当我们从父页面打开子页面或者从列表页面进入详细页面时,如果我们此时后退一步,就会发现之前浏览过的滚动记录不见了,页面被放在了最上面,就好像是第一次进入这个页面一样。这是因为spa页面中的url对应于路由容器页面。当页面路径与其不匹配时,页面组件将被卸载。当再次进入页面时,整个组件的生命周期将完全重复,包括一些数据请求和渲染,因此之前的滚动位置和渲染的数据内容将完全重置。

vue中的解决方案

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

内置组件保持活动状态

Keep-alive是Vue.js的内置组件,主要用于保存组件状态或避免重新渲染。

用法如下:

Keep-alive : include=' ['a ',' b ']' component : is=' view '/component/keep-alive keep-alive组件将匹配名称为' a '和' b '的子组件。匹配后将有助于组件缓存对组件进行优化,从而达到组件不被破坏的目的

实施原则

首先,简单看一下保活组件的内部实现代码。具体代码见Vue GitHub

创建了(){this。缓存=对象。创建(空)此。keys=[]}在创建的生命周期中,将使用对象创建一个缓存对象。创建方法,该方法将用作保存vnode节点的缓存容器。由Tip:对象. create(null)创建的对象并不比原型链更纯

render () { const slot=this。$ slots . default const VNode : VNode=getfirst component child(插槽)const componentOptions:vnode component options=vnode vnode。component options if(component options){//check pattern检查匹配项是否为缓存组件,主要根据include传入的名称对应constname3360。string=getcomponent name(component options)const { include,exclude}=thisif (//notincluded)如果此判断不匹配,将直接返回当前vnode (virtual dom) (include(!名称||!匹配(包括,名称)))|| //排除(排除名称匹配(排除,名称))){返回vnode } const { cache,keys }=这个const key:string=vnode.key==null //相同的构造函数可能会注册为不同的本地组件//因此仅cid是不够的(#3269)?组件选项。Ctor.cid (componentOptions.tag?` :3360 $ {componentoptions。标记} ` :'') :vnode。key if(cache[key]){//检查组件是否已经缓存在cache对象中。vnode直接使用组件实例vnode.component instance=cache[key]。component instance//使当前密钥新鲜移除(key,Key)密钥. push(Key)} else {//cache instance cache[Key]=vnode keys。push(key)//如果(this . maxkeys . length par sent(this . max))删除最旧的条目(cache,keys [0],keys,this)。_ vnode)} } vnode . data . keepalive=true }返回vnode | | (slot slot [0])}以上代码主要判断在render函数中是否是缓存渲染

vue保活内部实施的基本流程是:

首先通过getFirstComponentChild获取内部子组件,然后获取组件的名称,并匹配保活组件上定义的包含和排除属性。如果不匹配,就意味着组件没有被缓存,然后直接返回组件的vnode(vnode是一个虚拟dom树结构。由于原生dom上属性多,消耗巨大,使用这种模拟方法可以减少很多dom操作的开销。)如果匹配,则检查该实例是否已缓存在缓存对象中,如果匹配,则直接在当前vnode上覆盖缓存的vnode的componentInstance,否则,将vnode存储在缓存中。反应中的解决方案

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

本期GitHub,React项目,进行了相关讨论,开发维护人员给出了两种解决方式:

将数据与组件分开缓存。例如,您可以将状态提升到不会被卸载的父组件,或者将其放在像redux这样的侧缓存中。为此,我们还在开发一种应用编程接口上下文。不要卸载要保持活动状态的视图,只需使用style={{display:'none'}}属性隐藏它们。

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

在React中,页面数据、滚动条等信息通过redux或mobx中的状态管理进行缓存,从而达到缓存页面的效果。

component DidMount(){ const { app : { DataSorce=[],scrollTop},loadData }=this.propsIf (datasource。length){//判断redux//中是否有数据源,有数据则不再加载回单,只恢复滚动状态window.scrollTo(0,scroll top);} else {//如果没有数据,请求数据源this . props . LoadDATa();//redux } } handleclik=()={保存当前滚动距离const scroll top=document . document element . scroll top | | document . body . scroll top点击进入下一页前;const { SavescrollTop }=this . props;savescroll top(scroll top);}首先,我们可以在redux中为页面定义异步动作,并将请求的数据放入集中存储(redux的具体用法不详细说明)。在sotre中,我们可以保存当前页面的数据源、滚动条高度以及其他可能用来帮助我们恢复状态的分页数据。

在componentDidMount的生命周期中,首先根据redux中存储的对应字段判断数据源是否已经加载。如果数据已被缓存,您将不再请求数据源,而只恢复存储中存储的一些滚动条位置信息。如果尚未请求数据,则使用redux中定义的异步操作来请求数据,然后将数据存储在reducer中的存储中。在渲染函数中,我们只需要读取redux中存储的数据。

为了保留需要缓存的页面的一些状态信息,比如滚动条、分页、操作状态等,我们可以在进行相应的操作时,将这些信息存储在redux的存储中,这样在恢复页面时,就可以逐个读取和恢复这些对应的状态。

2.使用“显示”属性切换和显示隐藏的路由组件

如果希望显示属性切换并显示隐藏的路由组件,必须首先确保在url更改时不会卸载路由组件。react-Router中使用最多的route组件可以通过我们定义的path属性与页面路径进行匹配,并呈现相应的组件,从而保持UI和URL同步变化。

首先,简单看一下Route组件githubroute.js的实现

返回(RouterContext。提供者值={道具} {儿童!isEmptyChildren(儿童)?属性来确定是否呈现该组件?组件?React.createElement(组件、道具):渲染?render(props): null : null }/RouterContext。提供者);上面的代码出现在key render方法末尾的返回中

Route组件将根据props对象中的匹配属性决定是否渲染组件。如果匹配匹配,它将使用Route组件上传递的组件或渲染属性来渲染相应的组件,否则将返回null。

然后追根溯源,我们在道具对象中找到了匹配的定义:

const location=this . props . location | | context . location;const match=this . props.computed match?Switch已经为我们计算出了匹配的路径?matchPath(location.pathname,this . props): context . match;const props={.上下文、位置、匹配};上面的代码显示匹配将首先从组件的this.props中的computedMatch属性进行判断:如果this.props中有computedMatch,使用定义的computedMatch属性直接赋值匹配;否则,如果存在this.props.path,将使用matchPath方法根据当前位置. pathname判断是否匹配

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

//Switch已经为我们计算了匹配。这个评论说匹配已经在switch组件中为我们计算过了。

接下来,让我们看一下交换机组件:

“切换”组件将只将第一个按位置匹配的“路由”或“重定向”作为子元素呈现

让我们打开Switch组件的实现源代码:

让元素,匹配;//定义最后返回的组件元素,并匹配变量react.children.foreach(这。props.children,child={ if(match==null react . is valid element(child)){//如果match没有内容,输入判断元素=childconst path=child . props . path | | child . props . from;Match=path //三元表达式只有在匹配之后才会分配一个对象进行匹配,否则Match将始终为null?匹配路径(位置.路径名,{ 0.child.props,path }): context . match;} });返回匹配?React.cloneElement(元素,{ location,computed match : match }): null;首先,我们发现computedMatch属性在React.cloneElement方法中,该方法将合并通过追加到克隆组件元素定义的属性,并返回克隆生成的React组件,这意味着新道具属性被传递到组件中,新组件被返回。

上面找到的computedMatch的值匹配也是根据matchPath来确定的,match path是reactirouter中的一个API。此方法将根据您传入的第一个参数pathname和要匹配的第二个props属性参数来确定它是否匹配。如果匹配,则返回一个对象类型并包含相关属性;否则,它返回null。

在反应方法上。孩子们。对于每个循环子元素,matchPath方法判断当前路径名是否匹配,如果匹配,则为定义的match变量赋值。因此,分配匹配后,后续循环将不会执行匹配分配,因为Switch组件将只呈现第一次匹配它的组件。

3.实现路由缓存组件

我们知道开关组件将只渲染第一个匹配的子组件。如果所有匹配的组件都可以渲染,然后只使用块而不使用任何显示来切换是否显示,则实现第二种解决方案。

参照交换机组件封装路由缓存组件:

从“React”导入React;从“道具类型”导入道具类型;从“react-router”导入{ match path };从“react-router-dom”导入{ Route };类RouteCache扩展了React。组件{ static ProTypeS={ include : ProTypeS . oneofTYPe([ProTypeS . bool,ProTypeS . array])};缓存={ };//缓存加载的组件render () {const {children,include=[]}=this . props;回来反应。孩子们。map (children,child={if (react。是有效的元素(子元素)){//验证它是否是react元素const {path}=子元素。道具;const match=match path(location . pathname,{ 0.child.props,path });if(match(include==true | | include。includes(path)){//如果匹配,则将对应路径的computedMatch属性添加到缓存对象中。//当include为true时,缓存所有组件,当include为数组时,缓存这个对应的组件。缓存[路径]={ computed match 3360 match };}//可以在computedMatch中添加显示属性,得到const clone props=this . cache[path]object . assign(this . cache[path])。computedmatch,{display: match?block ' : ' none ' });return div style={ { display : match?block ' : ' none ' } } { react . cloneelement(child,{ computedmatch : clonepros })}/div;}返回null});} }//使用Routecache include={['/login ','/home ']} route path='/log in ' component={ log in }/route path='/home ' component={ app }//route cache阅读源代码后,我们知道route component将根据其this.props进行计算。

我们在组件内部创建一个缓存对象,并将匹配组件的computedMatch属性写入缓存对象。这样,即使url不再匹配,也可以通过读取缓存对象中的路径值并使用react.clonelelement方法,将computedMatch属性分配给组件的道具。这样,缓存的路由组件将一直呈现,组件不会被卸载。

因为多个路由组件可能被包装在组件内部,所以使用React。方法来回收其中包含的所有子组件。

为了正确显示UI和路由的对应关系,我们可以通过当前计算得到的匹配属性隐藏不匹配的组件,只显示匹配的组件。如果不想在组件外部包装div,也可以通过this.props.match中的display属性在组件内部切换显示组件

以vue keep alive的形式设置一个包含参数API。当参数为true时,缓存所有内部子组件,当参数为array时,缓存相应的路径组件。

使用效果

最初,从未被url匹配的组件将不会被呈现,并且里面的dom结构是空的。

当切换到相应的组件时,当前组件将被呈现,而先前匹配的组件将不会被卸载,而只会被隐藏

在输出日志中,我们可以看到,当我们不断地来回切换时,componentDidMount生命周期只执行一次,我们可以在props.match中得到当前的显示值

4.此外,一些第三方组件模块可用于实践缓存机制:

react-keeperract-路由器-缓存-路由器react-实时路由

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

版权声明:vue spa应用中的路由缓存问题及解决方案是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。