前端路由实现与 react-router 源码分析 React
2017-03-02 09:19
1066 查看
在单页应用上,前端路由并不陌生。很多前端框架也会有独立开发或推荐配套使用的路由系统。那么,当我们在谈前端路由的时候,还可以谈些什么?本文将简要分析并实现一个的前端路由,并对 react-router 进行分析。
一个极简前端路由实现
说一下前端路由实现的简要原理,以 hash 形式(也可以使用 History API 来处理)为例,当 url 的 hash 发生变化时,触发 hashchange 注册的回调,回调中去进行不同的操作,进行不同的内容的展示。直接看代码或许更直观。1234567891011121314151617 | function Router() { this.routes = {}; this.currentUrl = '';}Router.prototype.route = function(path, callback) { this.routes[path] = callback || function(){};};Router.prototype.refresh = function() { this.currentUrl = location.hash.slice(1) || '/'; this.routes[this.currentUrl]();};Router.prototype.init = function() { window.addEventListener('load', this.refresh.bind(this), false); window.addEventListener('hashchange', this.refresh.bind(this), false);}window.Router = new Router();window.Router.init(); |
init 监听浏览器 url hash 更新事件
route 存储路由更新时的回调到回调数组routes中,回调函数将负责对页面的更新
refresh 执行当前url对应的回调函数,更新页面
Router 调用方式以及呈现效果如下:点击触发 url 的 hash 改变,并对应地更新内容(这里为 body 背景色)
1234567891011 | <ul> <li><a href="#/">turn white</a></a> <li><a href="#/blue">turn blue</a></li> <li><a href="#/green">turn green</a></li></ul><ul> <li><a href="#/">turn white</a></li> <li><a href="#/blue">turn blue</a></li> <li><a href="#/green">turn green</a></li></ul> |
1234567891011121314 | var content = document.querySelector('body');// change Page anythingfunction changeBgColor(color) { content.style.backgroundColor = color;}Router.route('/', function() { changeBgColor('white');});Router.route('/blue', function() { changeBgColor('blue');});Router.route('/green', function() { changeBgColor('green');}); |
以上为一个前端路由的简单实现,点击查看完整代码,虽然简单,但实际上很多路由系统的根基都立于此,其他路由系统主要是对自身使用的框架机制的进行配套及优化,如与
react 配套的 react-router。
react-router
分析
react-router
与 history 结合形式
react-router 是基于 history 模块提供的 api 进行开发的,结合的形式本文记为 包装方式。所以在开始对其分析之前,先举一个简单的例子来说明如何进行对象的包装。可看到 historyModule 中含有机制:historyModule.updateLocation() -> listener( ),Router 通过对其进行包装开发,针对 historyModule 的机制对 Router 也起到了作用,即historyModule.updateLocation() 将触发 Router.listen 中的回调函数 。点击查看完整代码
这种包装形式能够充分利用原对象(historyModule )的内部机制,减少开发成本,也更好的分离包装函数(Router)的逻辑,减少对原对象的影响。
react-router
使用方式
react-router 以 react component 的组件方式提供 API, 包含 Router,Route,Redirect,Link 等等,这样能够充分利用 react component 提供的生命周期特性,同时也让定义路由跟写 react component 达到统一,如下1234567891011 | render(( <Router history={browserHistory}> <Route path="/" component={App}> <Route path="about" component={About}/> <Route path="users" component={Users}> <Route path="/user/:userId" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>), document.body) |
react-router 还提供的 Link 组件(如下),作为提供更新 url 的途径,触发 Link 后最终将通过如上面定义的路由表进行匹配,并拿到对应的 component 及 state 进行 render 渲染页面。
1 | <Link to={`/user/89757`}>'joey'</Link> |
从点击
Link 到 render 对应 component ,路由中发生了什么
为何能够触发
render component ?
主要是因为触发了 react setState 的方法从而能够触发 render component。从顶层组件 Router 出发(下面代码从 react-router/Router 中摘取),可看到 Router 在 react component 生命周期之组件被挂载前 componentWillMount 中使用 this.history.listen 去注册了 url 更新的回调函数。回调函数将在 url 更新时触发,回调中的 setState 起到 render 了新的 component 的作用。
1234567891011121314 | Router.prototype.componentWillMount = function componentWillMount() { // .. 省略其他 var createHistory = this.props.history; this.history = _useRoutes2['default'](createHistory)({ routes: _RouteUtils.createRoutes(routes || children), parseQueryString: parseQueryString, stringifyQuery: stringifyQuery }); this._unlisten = this.history.listen(function (error, state) { _this.setState(state, _this.props.onUpdate); });}; |
12345678 | function listen(listener) { return history.listen(function (location) { // .. 省略其他 match(location, function (error, redirectLocation, nextState) { listener(null, nextState); }); });} |
使用了 history 模块的 listen 注册了一个含有 setState 的回调函数(这样就能使用 history 模块中的机制)
回调中的 match 方法为 react-router 所特有,match 函数根据当前 location 以及前面写的 Route 路由表匹配出对应的路由子集得到新的路由状态值 state,具体实现可见 react-router/matchRoutes ,再根据 state 得到对应的 component ,最终执行了 match 中的回调 listener(null, nextState) ,即执行了 Router 中的监听回调(setState),从而更新了展示。
以上,为起始注册的监听,及回调的作用。
如何触发监听的回调函数的执行?
这里还得从如何更新 url 说起。一般来说,url 更新主要有两种方式:简单的 hash 更新或使用 history api 进行地址更新。在 react-router 中,其提供了 Link 组件,该组件能在 render 中使用,最终会表现为 a 标签,并将 Link 中的各个参数组合放它的 href 属性中。可以从 react-router/ Link 中看到,对该组件的点击事件进行了阻止了浏览器的默认跳转行为,而改用 history 模块的 pushState 方法去触发 url 更新。1234567891011121314151617 | Link.prototype.render = function render() { // .. 省略其他 props.onClick = function (e) { return _this.handleClick(e); }; if (history) { // .. 省略其他 props.href = history.createHref(to, query); } return _react2['default'].createElement('a', props);};Link.prototype.handleClick = function handleClick(event) { // .. 省略其他 event.preventDefault(); this.context.history.pushState(this.props.state, this.props.to, this.props.query);}; |
= url,调用哪个是根据我们一开始创建 history 的方式。
更新 url 的显示是一部分,另一部分是根据 url 去更新展示,也就是触发前面的监听。这是在前面 finishTransition 更新 url 之后实现的,调用的是 history/createHistory 中的 updateLocation 方法,changeListeners 中为 history/createHistory 中的 listen 中所添加的,如下
1234567891011 | function updateLocation(newLocation) { // 示意代码 location = newLocation; changeListeners.forEach(function (listener) { listener(location); });}function listen(listener) { // 示意代码 changeListeners.push(listener);} |
总结
可以将以上 react-router 的整个包装闭环总结为回调函数:含有能够更新 react UI 的 react setState 方法。
注册回调:在 Router componentWillMount 中使用 history.listen 注册的回调函数,最终放在 history 模块的 回调函数数组 changeListeners 中。
触发回调:Link 点击触发 history 中回调函数数组 changeListeners 的执行,从而触发原来 listen 中的 setState 方法,更新了页面
至于前进与后退的实现,是通过监听 popstate 以及 hashchange 的事件,当前进或后退 url 更新时,触发这两个事件的回调函数,回调的执行方式 Link 大致相同,最终同样更新了 UI ,这里就不再说明。
react-router 主要是利用底层 history 模块的机制,通过结合 react 的架构机制做一层包装,实际自身的内容并不多,但其包装的思想笔者认为很值得学习,有兴趣的建议阅读下源码,相信会有其他收获。
相关文章推荐
- 前端路由实现与 react-router 源码分析
- 前端路由实现与 react-router 源码分析
- 前端路由实现与 react-router 源码分析 React
- 前端路由实现与 react-router 源码分析
- 前端路由实现与 react-router 源码分析
- 深入Vue-Router源码分析路由实现原理
- vue-router 源码实现前端路由的两种方式
- 详解前端路由实现与react-router使用姿势
- 前端开发学习之——利用模板实现涉及url问题时的bug分析及解决(chrome源码)
- React-Router 4.2 的嵌套路由实现
- react-router(v3)切换页面时不刷新页面,实现显示和隐藏子路由组件
- CI框架源码完全分析之核心文件(路由)Router.php
- CTP源码分析8 CTP路由引擎结构及源码分析(二)----源码实现解读(一)
- React-Router 4 路由嵌套实现的分步表单
- 基于RTL819X实现的Router/AP的源码分析[一]
- ARouter 拦截器与路由的实现(源码分析)
- 浅析前端路由简介以及vue-router实现原理
- 使用react-router-dom优雅的实现页面(路由)跳转, 而且保持当前页面状态
- CI框架源码完全分析之核心文件(路由)Router.php
- React-router4路由监听的实现