2

前言

在前端单页面应用里面,路由是比较重要的部分,笔者的上一篇博文简单的路由介绍了简单的路由内部机制,本文则将分析react-router的内部机制。

介绍

react-router为react提供路由管理,为基于jsx格式的app系统提供了方便的切换页面功能。
它在前端提供给了2种方式,通过hashchange或者浏览器原生的history api进行地址更新,上一篇介绍了hash的方式,本文则以history api的形式切入分析。

代码剖析

路由配置

react-router本生为react组建,内部组建如Router,Route,IndexRoute, Redirect,Link等。
以下是摘自react-router example的路由配置

<Router history={withExampleBasename(browserHistory, __dirname)}>
    <Route path="/" component={App}>
        <IndexRoute component={Index}/>
        <Route path="/about" component={About}/>
        <Route path="users" component={Users}>
            <IndexRoute component={UsersIndex}/>
            <Route path=":id" component={User}/>
        </Route>
    </Route>
</Router>

点此获取完整代码

对应结构图

在初始化过程中他会以children形式读入Router生命周期内,在被转化为数组,此时它内部的结构如下
图片描述

路由初始化

初始化browserHistory对象

react-router依赖history^2.0模块生成的history对象,然后在Router生命周期componentWillMount中加入对应的封装如basename,query
useQueries.js 对history对象内的方法进行封装

function listen(listener) {
  return history.listen(function (location) {
    listener(addQuery(location))
  })
}

// Override all write methods with query-aware versions.
function push(location) {
  history.push(appendQuery(location, location.query))
}

useBasename.js 对history对象内的方法进行封装

   function listen(listener) {
      return history.listen(function (location) {
        listener(addBasename(location))
      })
    }

    // Override all write methods with basename-aware versions.
    function push(location) {
      history.push(prependBasename(location))
    }

Router.js 对history对象增加setRouteLeaveHook钩子函数以及isActive函数

最终生成router对象 以this.router = router存在Router组建内部,this.history 已过时(issues),不建议使用

初始化监听事件

this._unlisten = transitionManager.listen(function (error, state) {
  if (error) {
    _this.handleError(error);
  } else {
    _this.setState(state, _this.props.onUpdate);
  }
});


function listen(listener) {
changeListeners.push(listener);
if (location) {
  listener(location);
} else {
  var _location = getCurrentLocation();
  allKeys = [_location.key];
  updateLocation(_location);
}

此时整体初始化完毕

改变路由

  <Link to="about" activeStyle={ACTIVE}>/</Link>

以一次Link点击为例

  • 触发Link组建的handleClick方法
  • 调用router对象 push方法
  • 拼装location对象
  • 改变url栏的地址
  • 调用updateLocation 触发changeListeners内的所有监听事件
  • 回调函数内调用match方法,根据location对象正则匹配router对象,匹配出对应的组建 执行runLeaveHooks钩子
  • 调用Router组建的setState(nextstate)
  • Router-context组建的render方法调用createElement
  • 调用react.createElement
  • 完成渲染

简洁的流转图

放上一个本人总结的一个简单流转过程图
图片描述

详细流程图

对简洁的流程图熟悉之后,则可深入了解内部机制的细节如下图
图片描述

归纳

虽然源码繁琐复杂,但是内部的核心仍是围绕着下面3块动作做一系列的封装.

  • 注册监听事件:封装history对象的,生成router对象存储在Router内,并通过其注册监听事件,绑定相应的回调函数
  • 触发监听事件:通过Link/browserHistory.push/浏览器回退快进/dom ready 等四种方式触发回调函数
  • 回调函数: Router内的setState(next)最终触发react.createElement进而更新UI

图片描述

最后

本文有什么不完善的地方,或者流程图有待改进的地方,敬请斧正。


朱建
400 声望70 粉丝

有空的时候给自己设定一个小目标。