vue spa应用中的路由缓存问题与解决方案(3)

我们翻开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方法中,会将追加定义的属性合并到该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. 另外的也可以采用一些第三方组件模块来实习缓存机制:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/9fd02ebfacd2128b99fa263f7bf8bd48.html