前言
从事前端工作的第一份任务便是和同事协作构建一个大型SPA——企业后端管理平台,应用架构类似于企业微信·管理平台。使用React技术栈来完成前端开发,React-Router成为了必不可少的利器,无刷新的组件切换让用户体验更佳。工欲善其事必先利其器,此文用于学习总结React-Router。
解决的问题
使用路由可以自如流畅得向应用中添加,切换组件,并保持页面与URL间的同步,这在SPA中应用广泛。使用React-Router我们不需要手动寻找需要渲染的组件,无需编写复杂的逻辑,只需完成相应的路由配置,其它交给路由来解决。
用例
1 | import React from 'react' |
Router是一个React组件,作为路由容器来使用,Route中所包含的便是路由的规则。在使用时我们可以使用页面的层次关系来搭建路由规则。
在Route中有两个重要的属性:
- path属性置顶路由的匹配规则,将显示在浏览器的地址栏在域名后面。path属性省略时,总是会加载这个路由节点的指定组件。
- component属性指定URL匹配此路由规则时,所要挂载的组件。
通过上面的配置,此应用会以如下方式渲染URL:
URL | 组件 |
---|---|
/ | App |
/about | App -> About |
/inbox | App -> Inbox |
/messages/[someid] | App -> Message |
路径语法
路由路径是匹配一个(或一部分)URL的一个字符串模式。
你是否观察到路由规则的第三条有些不太一样,它的路径是path=”messages/:id”,并未确切的指定一个路由地址。
在React-Router中有几种特殊的匹配规则:
:paramName
匹配一段位于/
、?
或#
之后的URL。()
在其内部的规则被是可选的,匹配时可忽略。*
匹配任意字符(非贪婪的)知道下一个字符或者整个URL的末尾。
1 | <Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan |
我们可以使用如用例的相对地址,也可以使用如下的绝对地址,当路由规则嵌套很深时,这大有用处,可以简化我们的路由规则。
1 | React.render(( |
通过上面的配置,此应用会以如下方式渲染URL:
URL | 组件 |
---|---|
/ | App -> Index |
/about | App -> About |
/inbox | App -> Inbox |
/messages/[someid] | App -> Inbox -> Message |
提醒: 绝对路径在动态路由中无法使用。
History
React-Router是建立在history之上的。history知道如何去监听浏览器地址栏的变化, 并解析这个URL转化为location对象, 然后router使用它匹配到路由,最后正确地渲染对应的组件。
常用的history有三种形式:
- browserHistory
- hashHistory
- createMemoryHistory
使用方法是将history传入
1 | // JavaScript 模块导入(译者注:ES6 形式) |
React-Router推荐使用BrosbrowserHistoryerHistory
,它使用浏览器中的History API来处理URL。服务器需要进行相应配合,需要服务器作好处理URL的准备。hashHistory
使用URK中的hash(#)部分创建形如 example.com/#/some/path 的路由。createMemoryHistory
用作SSR(服务端渲染),此处不作深入了解。
IndexRoute 和 Redirect
IndexRoute
当我们访问的链接为www.demo.html/
时,页面将加载App组件。但是此时,App的render中的this.props.children为undefined,因为我们的路由并不匹配App的子组件。此时,我们可以使用IndexRoute
来设置一个默认页面。改造路由如下:
1 | React.render(( |
此时,App的render中的this.props.children将会是
Redirect
思考一个问题,如果在地址栏输入了路由规则中不匹配的URL,如www.demo.html/#/noroute
,会发生什么?页面会一片空白,空空如也。
我们有这样一个需求,访问www.demo.html/#/inbox
时,我想让地址跳转到www.demo.html/#/about
,渲染About组件,或是修正上面的空白错误,跳转到About组件。
要解决这样的问题,我们可以使用Redirect来进行重定向。
1 | React.render(( |
路由跳转
路由跳转可以分为内部跳转和外部跳转,内部跳转使用Link来完成,原理如a标签一般。组件外跳转可使用push方法来完成。
Link
Link组件用于取代a标签,生成一个链接,允许用户点击后跳转到另一个路由,如用例中的使用方法。activeStyle属性可以为当前路由添加样式。或者使用activeClassName为当前路由添加class。
1 | <Link to="/about" activeStyle={{color: 'red'}}>About</Link> |
push
需要在提交表单,点击按钮时跳转,我们可以使用push方法完成。
1 | // somewhere like a redux/flux action file: |
IndexLink
如果你在这个app中使用<Link to="/">Home</Link>
,它会一直处于激活状态,activeStyle和activeClassName会失效,或者说总是生效。因为所有的路由规则的开头都是 / 。这确实是个问题,因为我们仅仅希望在Home被渲染后,激活并链接到它。如果需要在Home路由被渲染后才激活的指向 / 的链接,即实现精确匹配,/
只匹配根路由,可以使用 <IndexLink to="/">Home</IndexLink>
。
或者使用Link组件的onlyActiveOnIndex属性,也可以达到同样的效果。
1 | <Link to="/" activeClassName="active" onlyActiveOnIndex={true}> |
动态路由
React-Router(Versoin 4)实现了动态路由。
对于大型应用来说,一个首当其冲的问题就是所需加载的JavaScript的大小。程序应当只加载当前渲染页所需的JavaScript。有些开发者将这种方式称之为“代码分拆” —— 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。React-Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。Route可以定义 getChildRoutes,getIndexRoute 和 getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React-Router 会逐渐的匹配 URL 并只加载该URL对应页面所需的路径配置和组件。
1 | const CourseRoute = { |
Hook生命周期钩子
Route可以定义onEnter和onLeave两个hook,用于捕捉进入路由和离开路由的时间点,执行一些操作,如完成权限验证。
在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。和事件捕获与事件冒泡的机制相同。
1 | <Router> |
上面的代码中,如果用户离开/messages/:id,进入/about时,会依次触发以下的钩子。
1 | /messages/:id的onLeave |
routerWillLeave
是离开当前路由时的生命周期钩子。该方法如果返回false,将阻止路由的切换,否则就返回一个字符串,在离开route前提示用户进行确认。
1 | import { Lifecycle } from 'react-router' |
Corner
- 路由算法会根据定义的顺序自顶向下匹配路由,匹配第一个符合规则的路由。
- 可以通过
this.props.params.id
来获取/:id
中的id。 - URL的查询字符串/foo?bar=baz,可以用
this.props.location.query.bar
获取。