聊聊 React Router v4 的设计思想

React Router v4 发布已经有几个月了,但好像并没有得到太多人的青睐,大家(包括我们团队自己)还是习惯使用v2、v3版本。这一方面是因为v4版本是一次破坏性的升级,从v2、v3 升级到v4,必需要大量重写原有的路由相关的代码,对于已经稳定的项目,一般是不会轻易尝试这种变更的;另一方面,即使是新项目,很多开发者也依然选择使用v2、v3老版本,因为v4新的设计思想,意味着你必须改变原有的使用路由的思维,才能正确的使用新版本。

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

<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源码中看出这一点:

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 v2、v3的问题,是在React组件思想之外,设计了一套API,是一种侵入式的设计。React Router的作者意识到了这个问题,所以在v4中,对React Router 进行了重写,将Route作为普通React组件看待,每个Route也负责UI的渲染工作,让React Router在React的大框架下运转。我们用v4版本实现上面的例子:

<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方法源码中印证这个流程:

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
      )
    )
  }
}

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

从上面的分析,可以得出动态路由的一个优点是,它会同时负责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本大前端精选书籍!

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容