亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

vue spa應用中的路由緩存問題與解決方案

2019-11-19 11:25:14
字體:
來源:轉載
供稿:網友

單頁面應用中的路由緩存問題

通常我們在進行頁面前后退時,瀏覽器通常會幫我們記錄下之前滾動的位置,這使得我們不會在每次后退的時候都丟失之前的瀏覽器記錄定位。但是在現在愈發流行的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'}}屬性去隱藏它們。

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路徑組件。

使用效果

在最初時,從未被url匹配過的組件不會被渲染,里面的dom結構是空的。

當切換到對應組件時,當前的組件被渲染,而之前已匹配的組件不會被卸載,只是被隱藏

在輸出日志中可以看到,當我們不停的來回切換時,componentDidMount生命周期也只執行一次,在props.match中我們可以獲取到當前的display值。

4. 另外的也可以采用一些第三方組件模塊來實習緩存機制:

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产国产精品人在线视| 91高潮精品免费porn| 日韩欧美国产免费播放| 欧美最近摘花xxxx摘花| 国产精品自产拍在线观看中文| 青青草99啪国产免费| 亚洲精品大尺度| 97超碰蝌蚪网人人做人人爽| 中文字幕v亚洲ⅴv天堂| 国产精品视频久| 亚洲国产成人91精品| 欧美国产日本高清在线| 麻豆乱码国产一区二区三区| 日韩成人av一区| 国产99久久久欧美黑人| 亚洲国产欧美日韩精品| 91高清免费在线观看| 日日骚久久av| 日韩av在线免费看| 国产三级精品网站| 日韩一区在线视频| 国产不卡av在线免费观看| 亚洲免费一级电影| 欧美成人国产va精品日本一级| 欧美另类极品videosbestfree| 久久影视电视剧免费网站| 欧美成人在线免费视频| 欧美激情亚洲国产| 日韩免费精品视频| 2025国产精品视频| 高清亚洲成在人网站天堂| 91在线视频九色| 亚洲性线免费观看视频成熟| 国产成人在线亚洲欧美| 欧美不卡视频一区发布| 97国产一区二区精品久久呦| 国产免费一区二区三区在线能观看| 久久免费视频这里只有精品| 国产精品av在线| 欧美在线亚洲一区| 成人性教育视频在线观看| 欧美日韩成人网| 亚洲精品中文字幕有码专区| 韩国国内大量揄拍精品视频| 国产精品香蕉国产| 欧美日韩成人在线观看| 久久影视电视剧免费网站| 一本色道久久88综合日韩精品| 日韩毛片中文字幕| 日本精品视频网站| 国产成人精品亚洲精品| 欧美性videos高清精品| 国产91在线播放九色快色| 日韩成人在线视频观看| 亚洲成人久久久| 国产精品日日摸夜夜添夜夜av| 久久香蕉国产线看观看av| 欧美成在线视频| 亚洲综合社区网| 这里只有精品久久| 91久久久久久久| 国产精品久久久久久久电影| 91免费在线视频| 日韩在线视频免费观看高清中文| 国产精品成久久久久三级| 一区二区国产精品视频| 日韩激情片免费| 久久久女女女女999久久| 国产精品免费在线免费| 久久精品电影网| 国产日韩在线观看av| 欧美黑人极品猛少妇色xxxxx| 精品久久久久久久大神国产| 国产在线拍偷自揄拍精品| 亚洲精品国精品久久99热一| 久久中文久久字幕| 亚洲国产精品嫩草影院久久| 国内精品久久久久久久| 日韩一区二区在线视频| 国产精品久久久久久久天堂| 欧美乱大交xxxxx另类电影| 亲爱的老师9免费观看全集电视剧| 精品国产乱码久久久久久婷婷| 欧日韩不卡在线视频| 亚洲国产古装精品网站| 中文字幕av一区二区三区谷原希美| 欧美成人在线影院| 亚洲欧美国产一区二区三区| 国产97人人超碰caoprom| 欧美大片免费观看| 奇门遁甲1982国语版免费观看高清| 欧美亚洲午夜视频在线观看| 国产日韩欧美一二三区| 久久久久久久激情视频| 日本精品一区二区三区在线| 国产精品wwww| 国产成人av在线| 国产精品久久久久久久电影| 国产精品美乳一区二区免费| 精品国产乱码久久久久久虫虫漫画| 激情懂色av一区av二区av| 亚洲成人久久久久| 日韩中文字幕精品视频| 国产在线观看精品| 久久亚洲欧美日韩精品专区| 成人免费观看网址| 成人免费视频网址| 日本中文字幕成人| 日韩电影在线观看永久视频免费网站| 日本高清不卡的在线| 精品国产一区二区三区久久久| 精品国产欧美一区二区五十路| 亚洲欧洲高清在线| 国产一区二区三区视频在线观看| 日韩黄在线观看| 国产精品色悠悠| 国产精品∨欧美精品v日韩精品| 欧美激情在线狂野欧美精品| 欧美电影在线观看完整版| 欧美激情videoshd| 国产女人18毛片水18精品| 国产精品ⅴa在线观看h| 一本色道久久综合亚洲精品小说| 亚洲成人三级在线| 疯狂做受xxxx高潮欧美日本| 欧美日韩在线视频首页| 最近2019年手机中文字幕| 91精品国产色综合久久不卡98| 久久全球大尺度高清视频| 欧美高清在线视频观看不卡| 久久伊人精品天天| 国产成人综合久久| 欧美在线观看视频| 国产精品高潮粉嫩av| 国产精品视频色| 精品视频9999| 久久99久久久久久久噜噜| 欧美高清视频免费观看| 欧美日韩中文字幕在线| 国内精品在线一区| 欧美日韩国产丝袜另类| 久久久久久久久久av| 欧美在线xxx| 欧美精品www| 亚洲午夜激情免费视频| 欧美性xxxxxxxxx| 68精品国产免费久久久久久婷婷| 性色av一区二区三区免费| 亚洲自拍偷拍色图| 亚洲成av人影院在线观看| 九九热r在线视频精品| 国产欧美一区二区三区久久| 一本大道久久加勒比香蕉| 欧美日韩福利电影| 日韩精品中文字幕久久臀| 欧美激情二区三区| 亚洲精品福利在线观看| 国产欧美一区二区三区久久| 欧美精品激情在线| 日韩av综合中文字幕| 亚洲免费视频网站| 欧美—级a级欧美特级ar全黄| 欧美性xxxxx| 亚洲欧美在线一区二区|