记录路由缓存那些事

后台管理系统,对路由缓存相关进行整理一下

首先,这边实现了,路由缓存功能,对于已经出现过的路由进行缓存(display: 'none')

但是发现在切换页面过程中,页面有点卡顿,所以这边把代码流程梳理一下。

        <Fragment>
          <Route exact path='/' render={() => <Redirect to='/dashboard' />} />
          {routes.map(
            ({ isKeepDisabled = false, path, ...dynamics }, key) => {
              const Component = isKeepDisabled ? Route : KeepRoute;
              return <Component shouldKeep={shouldKeep} key={key} exact path={path} component={dynamic({ app, ...dynamics })} />
            }
          )}
        </Fragment>

 

通过keepRoute进行路由缓存

const KeepRoute = withRouter(keep(Route))

keep代码实现如下:

import React from 'react'
import PropTypes from 'prop-types'
import matchPath from './matchPath'



const NORMAL = Symbol('NORMAL')
const SHOW = Symbol('SHOW')
const HIDE = Symbol('HIDE')
export const KeepContext = React.createContext(null)

export default function keep(Route) {

  return class extends React.Component {
    showPageCallbacks = []
    hidePageCallbacks = []
    routePhase = NORMAL
    state = {
      matched: null,
      pageRoot: null
    }
    static propTypes = {
      location: PropTypes.object.isRequired,
      shouldKeep: PropTypes.func
    }
    static getDerivedStateFromProps({ location, ...rest }) {
      return {
        matched: matchPath(location.pathname, rest)
      }
    }
    shouldComponentUpdate(nextProps, nextState) {
      let isKeep = true;
      const pageRoot = this.state.pageRoot;
      const { shouldKeep, location, ...rest } = nextProps;
      if (typeof shouldKeep === 'function') {
        isKeep = shouldKeep({
          root: pageRoot,
          match: function (pathname) {
            return matchPath(pathname, rest)
          }, path: nextProps.path
        })
      }
      if (!isKeep) {
        return true;
      }
      let shouldUpdate = Boolean(nextState.matched);
      if (pageRoot) {
        if (this.routePhase === NORMAL || (!this.state.matched && shouldUpdate && this.routePhase === HIDE)) {
          this.routePhase = SHOW
          pageRoot.style.display = 'block'
          shouldUpdate = false
          this.showPageCallbacks.forEach(callback => callback.call(null))
        } else if (this.state.matched && !shouldUpdate && this.routePhase === SHOW) {
          this.routePhase = HIDE
          pageRoot.style.display = 'none'
          this.hidePageCallbacks.forEach(callback => callback.call(null))
        }
      }
      return shouldUpdate
    }
    savePageRoot = (pageRoot) => {
      if (pageRoot) {
        this.setState({ pageRoot })
      }
    }
    onPageShow = (callback, isImmediate = true) => {
      if (typeof callback === 'function' && this.showPageCallbacks.indexOf(callback) === -1) {
        this.showPageCallbacks.push(callback)
      }
      if (isImmediate && this.routePhase === SHOW) {
        this.showPageCallbacks.forEach(callback => callback.call(null))
      }
    }
    onPageHide = (callback) => {
      if (typeof callback === 'function' && this.hidePageCallbacks.indexOf(callback) === -1) {
        this.hidePageCallbacks.push(callback)
      }
    }
    render() {
      const { matched, pageRoot } = this.state;
      return matched ? <div className={this.props.path} ref={this.savePageRoot} style={{ height: '100%' }}>
        <KeepContext.Provider value={{ matched, pageRoot, onPageShow: this.onPageShow, onPageHide: this.onPageHide }}>
          {<Route {...this.props} />}
        </KeepContext.Provider>
      </div> : null
    }
  }
}

代码解析:

const KeepContext = React.createContext(defaultValue);

 所以在路由切换的时候,对于该pageRoot进行判断,对现在页面进行隐藏,对于新打开的页面进行展示

点击顶部nav切换时,执行以下:

  handleClick(e, item) {
    e.preventDefault()
    e.stopPropagation()
    // console.log('tab nav????????', item)
    this.props.handleClick(item) // 就是下面的代码-切换路由
    const cuttent = item.pathname.split('/') // 表示当前打开的页面
    this.props.changeOpenKeys(['/' + cuttent[1]])
  }
    handleClick: (route) => {
      if (route.pathname === location.pathname) {
        return
      }
      dispatch(routerRedux.push({ pathname: route.pathname }))
    },
 

整体看下来,觉得代码没有什么问题,但是在我看来,页面在切换过程中,因为按钮是可配置的,所以切换时,路由变化,而此时还在当前页面内,所以按钮会消失,这时就会感觉页面是卡顿了一下。但是问了同事,同事说切换是立即执行的,display操作很快,不会有停留卡顿的效果,但是我排查了代码倒是不知道是什么原因导致了页面会卡顿一下再跳转。

看了别的项目的系统,他们的系统较之我们,更加复杂,而他们却没有切换卡顿的现象,这边看一下他们的系统,整理他们系统的路由实现。

 

基本思路:他们是使用了 shouldComponentUpdate ,对于组件进行了封装继承,当页面路由与当前页面不匹配时,不进行页面更新。

因为他们的组件都是使用的公共组件,所以在公共组件上封装了一层就行了。

我们的程序,每个页面都是单独写的,所以需要给每个页面都单独加上,因为之前可能某些页面也有shouldComponentUpdate,所以可能会导致不一定的错误,所以在下个版本把这个优化加上。

但是其实确实页面比较多,每个页面也不统一,可能有的页面还是用函数组件写的,所以下个版本做这个优化

其实,现在也在考虑这个问题,每个页面都是单独的一套,是否对于后面的优化存在隐患,因为真的每次增加某项东西,都需要给二十多个页面一一添加,维护起来不太方便,但是,哎,小菜鸡也不太会正确的重构这部分,等大佬动手吧。。

import React, { Component } from 'react'
import isEqual from 'lodash.isequal'

export default class BsPureComponent extends Component {
  // 重写shouldComponentUpdate
  shouldComponentUpdate (nextProps, nextState) {
    if (!isEqual(this.props, nextProps) || !isEqual(this.state, nextState)) {
      return true
    }
    return false
  }
}

/**
 * 组件类继承的基类,重写componentUpdate方法;
 * 1、pathname 、routerPathname不存在则渲染
 * 2、pathname与routerpathname不匹配则不渲染
 * 3、路由发生切换 且 pathname与routerpathname匹配阻止渲染
 */
export class PreventRender extends React.Component {
  shouldComponentUpdate (nextProps) {
    try {
      return isShouldpreventRender.call(this, nextProps)
    } catch (error) {
      return true
    }
  }
}

export function isShouldpreventRender (nextProps) {
  try {
    const pathname = this.context.router.pathname || ''
    const routerPathname = nextProps.router.location.pathname || ''
    if (!pathname || !routerPathname) return true
    if (this.props.routerChangeCount !== nextProps.routerChangeCount && pathname === routerPathname) { return false }
    return pathname === routerPathname
  } catch (error) {
    return true
  }
}

 

后面对于现在的项目进行改造,通过shouldComponentUpdate进行路由优化

优化如下:

 

组件主要通过,上面的搜索栏(SearchBar),和下面的列表页(ListTable)来进行优化

(然后,因为我真的取不到组件上的pathName,所以只能使用最笨的传参的方式了)

(这边先这样实现,后面问一下同事,有什么更好的方式实现~~)

首先搜索栏:

<SearchBarComp
        wrappedComponentRef={(searchFrom) => {
          this.searchFrom = searchFrom
        }}
        list={this.searchConfig()}
        searchInfo={isBatch ? searchInfo_batch : searchInfo}
        unflodFlag={isBatch ? unflodBatchFlag : unflodFlag}
        toggleUnflodFlag={this.changeUnflod}
        searchFn={this.handleSearch}
        resetFn={this.handleReset}
        pageName={'baseListSearch'}
        pathname={'/product/baseList'} // 这个是新加的表示组件的pathName
      />

然后用的是公共组件,在里面进行shouldComponentUpdate判断,哈哈简单写一下

  shouldComponentUpdate() {
    let result = true
    if (location.pathname !== this.props.pathname) {
      result = false
    }
    return result
  }

 

然后列表栏:

通过继承共同组件来实现,这边多写一个组件继承

class BaseListTable extends PreventRender {
  constructor(props, context) {
    super('/product/baseList')
    this.state = {}
  }
}
PreventRender组件实现:(就是对于周期函数的重写)

/**
 * 组件类继承的基类,重写componentUpdate方法;
 * 1、pathname 、routerPathname不存在则渲染
 * 2、pathname与routerpathname不匹配则不渲染
 * 3、路由发生切换 且 pathname与routerpathname匹配阻止渲染
 */
export class PreventRender extends React.Component {
 constructor(path) {
  super()
  this.path = path
 }
 shouldComponentUpdate(nextProps) {
  try {
   return isShouldpreventRender.call(this, nextProps)
  } catch (error) {
   return true
  }
 }
}

export function isShouldpreventRender(nextProps) {
 try {
  const pathname = location.pathname || ''
  const routerPathname = this.path || ''
  if (!pathname || !routerPathname) return true
  return pathname === routerPathname
 } catch (error) {
  return true
 }
}

完结撒花!

 

 

posted @ 2022-06-08 18:08  千亿昔  阅读(121)  评论(2编辑  收藏  举报