聊聊 React Router v4 的设计思想

React Router v4 发布已经有几个月了,但好像并无获得太多人的青睐,你们(包括咱们团队本身)仍是习惯使用v二、v3版本。这一方面是由于v4版本是一次破坏性的升级,从v二、v3 升级到v4,必须要大量重写原有的路由相关的代码,对于已经稳定的项目,通常是不会轻易尝试这种变动的;另外一方面,即便是新项目,不少开发者也依然选择使用v二、v3老版本,由于v4新的设计思想,意味着你必须改变原有的使用路由的思惟,才能正确的使用新版本。前端

React Router v4 最大的变动,不是API的变动,而是从静态路由到动态路由的变化。什么是静态路由呢?静态路由是一堆在应用运行前就已经定义好的路由配置,应用须要在启动时,加载这些配置,构建出整个应用的路由表,而后当接收到某一请求时,根据请求地址,到应用路由表中找到对应的处理页面或处理方法。不论是前端开发,仍是后端开发,只要涉及到路由,大部分状况下,其实咱们使用的都是静态路由。例如,React Router v3版本中,咱们会配置一个相似下面形式的路由:react

<Router history={browserHistory}>
  <Route path='/' component={App}>
    <Route path='about' component={About}>
    <Route path='contact' component={Contact}>
    // ...
  </Route>
</Router>
复制代码

它的基本工做流程是:Router组件根据全部的子组件Route,生成全局的路由表,路由表中记录了path与UI组件的映射关系,而后Router监听path的变化,当path变化时,根据新的path,找出对应所需的全部UI组件,按必定层级将这些UI组件渲染出来。后端

对于已经很熟悉静态路由使用方式的开发者来讲,上面的工做流程显得很天然,理解起来也绝不费力。既然如此,React Router的做者为何还要把这一切推翻呢?缘由是React Router不是普通的Router,它是“React”的Router。React致力于提供一个高效简洁的组件化方案,组件就是React的核心,在React的设计思想中,一切皆是组件。那么什么是组件呢?组件定义的是界面上一个区域的UI及UI的交互行为,关注点是UI。如今让咱们回头来看看上面静态路由的例子,是否是感受到什么奇怪的地方呢?虽然Route形式上是React组件,但它其实与UI无任何关系,它只是披着React组件的外衣,提供了一条路由配置项而已。咱们也能够从Route源码中看出这一点:markdown

const Route = createReactClass({
  // 省略无关代码

  /* istanbul ignore next: sanity check */
  render() {
    invariant(
      false,
      '<Route> elements are for router configuration only and should not be rendered'
    )
  }

})
复制代码

Route的render方法中,没有作任何UI渲染相关的工做,这确实不是一个正宗的React组件。固然你也能够用React Router的另外一种配置路由的方式:框架

const routes = {
  path: '/',
  component: App,
  childRoutes: [
    {
      path: 'about',
  	  component: About,
    },
    {
      path: 'contact',
  	  component: Contact,
    },
    // ...
  ]
}

<Router history={browserHistory} routes={routes} />
复制代码

如今你又能够义正词严的说,我没有使用Route这个伪组件了,此次和React的设计思想没有冲突了吧?好吧,让咱们再来看看其余部分。React Router v3提供了不少相似生命周期方法的API,例如onEnter, onUpdate, and onLeave ,用来为处于不一样阶段的路由提供钩子方法。可是,请不要忘了,React组件自己已经有一套很完善的生命周期方法了,若是一个Route就是一个组件,那么咱们彻底能够直接利用组件的生命周期方法,来做为路由不一样阶段的钩子方法。例如,咱们可使用componentDidMount 或 componentWillMount替代onEnter,使用 componentDidUpdate或 componentWillUpdate 替代onUpdate,使用componentWillUnmount替代onLeave。组件化

React Router v二、v3的问题,是在React组件思想以外,设计了一套API,是一种侵入式的设计。React Router的做者意识到了这个问题,因此在v4中,对React Router 进行了重写,将Route做为普通React组件看待,每一个Route也负责UI的渲染工做,让React Router在React的大框架下运转。咱们用v4版本实现上面的例子:this

<BrowserRouter>
  <div>
    <Route path='/' component={App} />
    <Route path={'/about'} component={About} />
    <Route path={'/contact'} component={Contact} />
  </div>
</BrowserRouter>
复制代码

但从表面上看,并不能很直观地看出Route工做机制的变化。这里作一简单说明:Route的做用不是提供路由配置,而是一个普通的UI组件,无论请求的路径是什么,Route组件老是会被渲染,只不过在Route内部会判断请求路径是否与当前的path匹配,若是匹配,就会把Route component属性指向的组件做为子组件渲染出来,若是不匹配,会渲染一个null。能够重新版Route 的render方法源码中印证这个流程:url

class Route extends React.Component {
  //...省略无关代码
  
  render() {
    const { match } = this.state
    const { children, component, render } = this.props
    const { history, route, staticContext } = this.context.router
    const location = this.props.location || route.location
    const props = { match, location, history, staticContext }

    return (
      component ? ( // component prop gets first priority, only called if there's a match
        match ? React.createElement(component, props) : null
      ) : render ? ( // render prop is next, only called if there's a match
        match ? render(props) : null
      ) : children ? ( // children come last, always called
        typeof children === 'function' ? (
          children(props)
        ) : !Array.isArray(children) || children.length ? ( // Preact defaults to empty children array
          React.Children.only(children)
        ) : (
          null
        )
      ) : (
        null
      )
    )
  }
}
复制代码

这种模式的路由就是动态路由。可见,动态路由发挥做用的时间是在组件渲染时,而不是经过提早配置的方式,在应用刚收到请求时,就已经知道该渲染哪些组件了。spa

从上面的分析,能够得出动态路由的一个优势是,它会同时负责UI的渲染工做,而不是单纯的路由配置工做。此外,动态路由的另一个优势是,你能够在任意时间、任意地点自由添加新的Route。例如,在上面的例子中,我想在About组件内定义两个新的路由,能够这么作:设计

<BrowserRouter>
  <div>
    <Route path='/' component={App} />
    <Route path={'/about'} component={About} />
    <Route path={'/contact'} component={Contact} />
  </div>
</BrowserRouter>

const About = (props) => (
  <div>
    <Route path={`${props.match.url}/a`} component={AboutA} />
    <Route path={`${props.match.url}/b`} component={AboutB} />
  </div>
)
复制代码

这样,当访问 /about/a 时,组件AboutA 会被做为About的子组件渲染,当访问 /about/b 时,组件AboutB 会做为About的子组件渲染。并且,/about/a 和 /about/b 咱们是直接定义到 About 组件内的,并不须要像静态路由那样作集中配置,充分体现了动态路由的灵活性。

总结一下,虽然React Router v4 重构了路由使用的思想,但却和React的设计思想更加切合,我的认为是一个巨大的进步。使用React Router v4 时,你须要忘掉之前使用静态路由的思惟方式,把路由当成普通组件看待,习惯了这个思惟转变后,你就会发现React Router v4的魅力所在了。


欢迎关注个人公众号:老干部的大前端,领取21本大前端精选书籍!