[干货]React-Keeper : 比React-Router更适合你的单页面路由器

#1

原文地址

https://zhuanlan.zhihu.com/p/25081540

了解React的同学一定了解React-Router,这个几乎是React单页面应用必备的路由框架。如果有足够多的开发经验,你一定会发现React-Router的很多问题,比如:没有页面缓存、不能传递属性、脱离JSX的动态加载和过滤器实现等,这些问题尤其在移动端表现问题更多。


这里我们来推荐一款更强大的React路由器:React-Keeper,一个比React-Router更灵活、更适用于移动端、路由功能更健壮的框架,React-Keeper除了基础功能更强大以外,特别对移动APP的路由做了增强,能够满足更丰富的移动端场景。

**React-Keeper**吸收了React-Router的思想,使用方式与React-Router相似度很大,都提供了Route组件和Link组件,基本可以实现React-Router的平滑迁移。React-Keeper的基础教程,可以参考其Github : Github [React-Keeper],这里我重点介绍一下React-Keeper的特性。

特性介绍

1. 可扩展路由

允许你在任何时间、任何组件内添加路由配置。如下面:我们可以在路由匹配的的组件Products中再添加路由组件。这种特性,对团队合作开发很友好,可以让路由配置也按模块化的切分;也非常适用于有动态加载需求的大型网站。(这个特性在React-Router最新版中也已经得到了支持)

const App = ()=> {
    return (
      <HashRouter>
        <div>
          <Route component={ Home } path="/"/>
          <Route component={ Products } path="/products"/>
        </div>
      </HashRouter>
    )
  }

  const Products = ()=> {
    return (
      <div>
        <Route component={ ScienceProducts } path="/sci" />
        <Route component={ DailiUseProducts } path="/dai" />
      </div>
    )
  }

  ReactDOM.render(<App/>, document.getElementById('root'))

2. 页面缓存

在移动开发中,我们经常会遇到这样的场景:在一个列表页浏览了很久,点击一项进入详情页,然后再返回到列表页,这时候我们希望列表页能保留在之前的状态(滚动位置、临时操作等),React-Router无法解决这个问题,在返回后列表页的DOM要重新加载,所以我们不得不重新手动找回之前的状态(滚动到之前的位置)。

这里我们需要一个页面缓存机制来解决这个问题。所谓页面缓存,即当地址与路由不匹配时,自动缓存页面的状态,当匹配时,再对页面进行还原。

页面缓存是React-Keeper的一个重要特性,其内部集成了缓存管理器,可以对路由组件的绑定与解绑进行代理,从而实现了页面缓存。React-Keeper提供了3种页面缓存方式,下面我们来分别进行介绍。

2.1 cache属性

所有添加了cache属性的路由组件,React-Keeper缓存管理器都会页面进行代理。在下面的示例中,Home、AboutUs页面会使用缓存代理:

class App extends React.Component {

  render() {

    return (
      <HashRouter>
        <div>
          <Route cache component={Home} path='/'/>

          <Route component={Host} path='/host' />

          <Route cache='parent' path='/aboutus' component={AboutUs}/>

        </div>
      </HashRouter>
    )
  }
}

ReactDOM.render(<App/>, document.getElementById('root'))

cache属性可以添加属性值,React-Keeper支持的属性值有root(default)、parent。

cache=‘root’(或cache)为永久缓存,只要根组件不解绑,页面将永久缓存。

cache='parent’为父组件缓存,在父组件不解绑的情况下会维持缓存状态。

2.2 CacheLink组件

React-Keeper额外提供了CacheLink组件,继承自Link,故有Link组件的所有特性,此外,其内部对接了缓存管理器,可以对链接跳转环节进行代理。

CacheLink缓存为临时缓存,当使用其新打开页面时,缓存管理器会临时缓存链接的来源页面,当返回时至之前页面(或路由状态变更)时,提取缓存页面以展示,并清除缓存。

这种特性适用于非常用列表页的缓存,使用方式如下:

<ul className='nav navbar-nav'>
  <li>
    <Link to='/'>Home</Link>
  </li>
  <li>
    <CacheLink to='/product/ASDFADF'>Detail</CacheLink>
  </li>
</ul>

3. 标签化过滤器

在React-Keeper中,我们可以为每一个Route组件单独定义多个过滤器,当过滤器验证通过后才能进行下面的操作,Route支持两类过滤器:绑定过滤器、解绑过滤器。

过滤器的使用场景,最常用的应该就是登录验证,对于某些需要登录后才能访问的资源,我们希望登录检测通过后再进行页面绑定,而不是先跳转页面再进行验证。下面是React-Keeper官网登录过滤的示例代码:

// Define a fllter, and run over it or not.

// receive 'props'
const loginFilter = (callback, props)=> {
  
  if(!props.host) {

    // dynamicly request data (use jQuery ajax)
    $.ajax({
      url: 'host/login.do',
      data: {},
      succeed: function(data){
        if(data.host){

          // run 'callback' function to enter next step (render component or next filter)
          callback()            
        }
      },
      error: function(){

      },
      dataType: 'json'
    })
  }
}

// Added to Route Component
// Single Filter
<HashRouter>
  <Route path='/user' component={User}, enterFilter={ loginFilter } />
</HashRouter>

// Multiple Filters
<HashRouter>
  <Route path='/user' component={User}, enterFilter={[ loginFilter, permitFilter1, permitFilter2 ] } />
</HashRouter>

4 标签化动态加载

React-Keeper支持动态组件加载,而动态加载使用方式也是非常简单,可以直接在Route组件行进行操作。使用方式如下:

<Route loadComponent={ (callback)=>{
  System.import('../Products.js').then((Products)=>{
      callback(Products)
    })
  } } path='/products'> 

在React-Keeper的内部处理中,当path匹配的时候,才会进行文件的加载,这对于大型的WEB应用无疑是非常必要的。

注意:过滤器的运行,在动态文件加载之后。

5 灵活的配置

  1. React-Keeper的Route组件支持自定义属性,并会将所有自定义的属性传递给要渲染的组件。

  2. React-Keeper的配置相当灵活,可以全部采用组件属性化的配置,比如index、cache、miss等,以下是Route组件所有的保留词:

index : 入口组件

miss : 地址不匹配时渲染的组件

cache : 缓存标记

redirect : 重定向地址 (当组件满足渲染条件时才会执行)

path : 匹配地址规则

component :要匹配的组件

loadComponent : 动态加载组件

enterFilter : 挂载过滤器

leaveFilter : 卸载过滤器

offDirtyCheck : 关闭脏检查。React-Keep会默认启用脏检查,以避免地址变更时不必要的渲染

<HashRouter>
  <div>
    <Route index component={Home} path='/'/>

    <Route cache component={Host} path='/host' />

    <Route miss path='/aboutus' component={AboutUs}/>

    <Route path='/other' redirect='/redirect'/>

  </div>
</HashRouter>

写在最后

读React-Keeper源码,发现内部有几点值得React开发者借鉴的地方:

  1. 可扩展路由的实现方式采用了订阅模式,进行Route的集中管理,通过减少监听事件保证了路由管理的效率。

  2. 默认使用数据脏检查,避免不必要的渲染。

  3. 缓存管理是重要的一个核心功能,React-Keeper内部集成了两个缓存管理器,并在每次地址变更时对缓存进行清理。

  4. 集成了地址匹配缓存以提高匹配的效率。

  5. 无状态组件(Stateless Component)的管理,使用react-funtional库将组件转换为有状态组件的方式。

React-Keeper还是一个比较新的框架,国内外实践的人还比较少。从源码级别看其实现,在前端世界众多而杂乱的开源框架中,算是质量很高的一个。

附录

Github : https://github.com/vifird/react-keeper

#2

既然是缓存,那么就不存在缓存页面的说法,而是缓存页面内的数据,缓存数据已经可以用redux很好的解决,对于不用redux的同学,使用router缓存是个好的选择。

#3

状态不只是包含数据,还有渲染状态、交互状态等。Redux是数据剥离一个很好的解决方案,可以解决数据状态的缓存问题,然而无法满足页面其他状态的缓存。
原生移动APP,都会自带缓存页面机制,比如Android的Activity,这样保证在返回页面的时候,能回复到之前的状态,然而在WEB APP中,没有这样一个框架可以缓存页面,而这个场景在移动端的场景需求量非常大。

1 Like
#4

项目中就遇到这个问题,需要保存上一个路由的数据和状态。
但是项目本身很小,自己对redux也不是很熟练,想来想去还是自己做处理。。。

方式是用一个父路由包含所有页面,然后再父路由里做数据存储和分发。
但是水平太挫写出来惨不忍睹,感觉还是需要一个这样的库,有些情境下还真是很需要的。。

#5

@rover5056
我们之前也是在使用React-Router,在项目中被折磨了无数次以后,决定要写这个框架。基于React-Router,但是比它更强大,更灵活,更实用,这也是我们做这个产品的目标。
现在在我们内部的产品中,已经完全使用React-Keeper代替了React-Router,开发人员也一直在不停的迭代产品。现在把它开源出来,也是希望能解决更多人的问题,能让更多有想法的人参与到这个项目中来一起提升产品。

#6

刷新网页还能读hash缓存吗?

#7

@hyy1115
现在不支持刷新的缓存读取,这个需要使用到浏览器的本地存储,路由器没有实现这样的功能。

#9

这个好像依然没有解决单页面网站,一个页面滑动会使下一个页面位置变动的问题,还是我不知道咋设置

#10

龙哥是你能质疑的吗?:rage:

#11

@blackoo00
有使用滚动插件吗?这个问题有可能是滚动插件导致的。

#12

如何记住滚动位置?有没有demo可以展示下

#13

我没有使用滚动插件,也是会有滚动条会影响下一个页面位置的变动。

#14

如果没有使用滚动插件,那滚动条可能是加在body标签上的,这样所有的内容会同时滚动,这里应该合理地选择滚动容器,可以避免这个问题。

#15

@vifird 先谢谢你们开源这么优秀的路由模块,谢谢

然后,想问一下 React-Keeper 是否支持 模态画廊

之所以这么问,是因为我们当前项目里面存在非常多的弹窗(非覆盖式,左右上下弹出式),弹窗出现是点击某个列表中的某条数据,我希望能更新 hashurl 来显示弹窗内容,而且我也可以通过复制 url 的方式在新页面定位到列表页,并且直接打开这个弹窗。

同时也想问下大约什么时候能够有一个集中式的文档说明(文档站点),单个 md 的文档看起来非常零碎,比较难串起来看。

#16

@lovefishs 可以的。
弹窗可以使用嵌套路由来实现,嵌套路由的优势是:在打开子组件之前会打开父组件,你们的场景,可以将底页面作为父组件,弹窗作为子组件,每个子组件有自己的URL,类似于下面的结构:

<Route path='/parent' component={ Parent }>
  <Route path='/chiild1' component={ Child1 }/>
  <Route path='/chiild2' component={ Child2 }/>
</Route>

这样可以通过/parent/child1来访问Child1页面,/parent/child2来访问Child2页面,并且Parent页面会预先加载。
Parent组件内需要嵌入子页面Child1和Child2,代码像这样:

const Parent = (props)=> {
  return (
    <div>
       <div>Parent页面内容</div>
       <div>{props.children}</div>
    </div>
  )
}

当然,按你们的场景,Child1 Child2应该要设计为fixed(或absolute)定位,也可以添加载入动画来优化视觉体验。

#17

@vifird 非常感谢您的耐心解答!谢谢!!

在使用 react-router 的时候我经常会有这样的需要: React-router 改变 params 时组件如何重新挂载。这个后面通过给组件指定 key 的方式算是解决了一部分,那么路由组件本身能不能感知到这种变化从而重建组件呢(或者一些更高效的方式)?

#18

这是一个很多路由库存在的问题,存在问题的原因是这样的:Route组件通过判定URL是否匹配来决定是否解绑、挂载组件,参数变化的前后,当前Route始终是匹配状态,所以没有进行解绑与重新挂载;改进这点的难度也是比较大的,一方面Route组件无法判定当前URL变化是否要强制重新渲染当前组件,另一方面进行重新挂载的成本是比较高的。我们也在考虑这方面的设计,目前还没有找到比较满意的解决方案。

当然,目前使用key的方式,或者使用componentWillReceiveProps(推荐)的方式可以实现大部分需求。

#19

@vifird 谢谢您的耐心解答!

准备在后面的项目里面实际使用 React-Keeper 试试,有相关问题会直接去 github 给你们提 issue,项目已 star ,当前希望能尽快的提供更详细的文档!

#20

好的,文档我们正在努力中~

#21

react-keeper如何监控路由的变化?