React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。
路由配置
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message}>
</Route>
</Route>
</Router>
),document.body)
这样的路由配置会将url与组件进行如下的对应关系:
- /:App
- /about:App->About
- /inbox:App->Inbox
- /inbox/messages/:id :App -> Inbox -> Message
可以看到这种Route之间的嵌套关系是如何加载组件的。
我们可以在父组件中使用this.props.children来获取嵌套的子组件:
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
//!!这里!!加载About或Index
{this.props.children}
</div>
)
}
})
const About = React.createClass({
render() {
return <h3>About</h3>
}
})
const Inbox = React.createClass({
render() {
return (
<div>
<h2>Inbox</h2>
//!!这里!!当url为/inbox/messages/:id 时会加载Message组件
{this.props.children || "Welcome to your Inbox"}
</div>
)
}
})
const Message = React.createClass({
render() {
return <h3>Message {this.props.params.id}</h3>
}
})
注意,这里可以在组件中使用this.props.params.id
来获取url中的:id参数。
IndexRoute
当 URL 为 / 时,此时App中的this.props.children
还是 undefined,这种情况可以使用一个IndexRoute来设置默认页面:
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message}>
</Route>
</Route>
</Router>
),document.body)
现在,当 URL 为 / 时,App组件中的this.props.children会是Dashboard组件,而url不为/时,则会像上面一样加载其他组件而不显示Dashboard。
绝对路径
针对上面的路由配置,我们只有在/inbox/messages/:id这一路径下才能加载出App -> Inbox -> Message的视图。如果有更深层次的组件嵌套那么url也会有更多的层级,是我们不想看到的。
使用绝对路径配置路由可以让UI从url中解耦出来:
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
{/* 使用 /messages/:id 替换 messages/:id */}
<Route path="/messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
和之前的path属性值对比,此时messages/:id
更改为/messages/:id
,这是一个绝对路径,脱离了/inbox这一层级,但是组件的嵌套关系还是没变的。
因此现在通过访问/messages/:id
就可以得到App -> Inbox -> Message视图。
提醒:绝对路径可能在动态路由中无法使用。
Redirect
为了使访问/inbox/messages/:id
的用户现在能访问到/messages/:id
,我们要对原来的url做一个重定向的工作。
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="/messages/:id" component={Message} />
{/* 跳转 /inbox/messages/:id 到 /messages/:id */}
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
</Route>
</Router>
), document.body)
现在当有人点击 /inbox/messages/5 这个链接,他们会被自动跳转到 /messages/5
Hook
Route可以定义 onEnter 和 onLeave 两个 hook,这些hook会在页面跳转时被触发。
这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。
- onLeave:在所有将离开的路由中触发,从子路由到最外层路由
- onEnter:在所有进入的路由中触发,从最外层路由到最后的子路由
如果一个用户点击链接,从 /messages/5 跳转到 /about,下面是这些 hook 的执行顺序:
- /messages/5 onLeave
- /inbox onLeave
- /about onEnter
可以看到,虽然绝对路径使url与ui嵌套关系解锁了,但是在跳转的时候还是会按照相应的嵌套关系,inbox的hook还是会被触发。
路由匹配原理
路由的三个属性用来决定是否匹配一个URL:
- 嵌套关系
- 路径语法
- 优先级
嵌套关系
React Router 使用路由嵌套的概念来让你定义 view 的嵌套集合,当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。
嵌套路由被描述成一种树形结构。React Router 会深度优先遍历整个路由配置来寻找一个与给定的 URL 相匹配的路由。
路径语法
路由路径是匹配一个(或一部分)URL 的 一个字符串模式。
其中有几个特殊的符号用法:
:paramName
匹配一段位于 / ? 或 # 之后的 URL。 命中的部分将被作为一个参数()
它内部的内容被认为是可选的*
匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat 参数
<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
这里再次说一下相对路径与绝对路径的区别:
- 相对路径:完整的路径将由它的所有祖先节点的路径和自身指定的相对路径拼接而成
- 绝对路径:使路由匹配行为忽略嵌套关系
优先级
路由算法会根据定义的顺序自顶向下匹配路由。
因此,当你拥有两个兄弟路由节点配置时,你必须确认前一个路由不会匹配后一个路由中的路径,造成后面的路径永远不会被匹配。
链接配置
在组件中使用<Link>
代替<a>
链接或者其他可点击元素,这样就能触发路由跳转:
<Link to="/home">Home</Link>
如果希望当前路由与其他路由有不同的样式,则可以使用Link组件的activeStyle属性:
<Link to="/about" activeStyle={{color: 'red'}}>About</Link>
<Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>
上面代码中,当前页面的链接会红色显示。
另一种做法是,使用activeClassName指定当前路由的Class:
<Link to="/about" activeClassName="active">About</Link>
<Link to="/repos" activeClassName="active">Repos</Link>
当前页面的链接的class会包含active。
IndexLink
如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件。
这是因为对于根路由来说,activeStyle和activeClassName会失效,或者说总是生效,因为/会匹配任何子路由。而IndexLink组件会使用路径的精确匹配:
<IndexLink to="/" activeClassName="active">
Home
</IndexLink>
上面代码中,根路由只会在精确匹配时,才具有activeClassName
History
React Router 是建立在 history 之上的。
history监听浏览器地址栏的变化,并解析这个URL转化为location对象,然后router使用它匹配到路由,最后渲染组件。
常用的 history 有三种形式:
- browserHistory
- hashHistory
- createMemoryHistory
在Router中传入history来使用:
import { browserHistory } from 'react-router'
<Router history={browserHistory}>
browserHistory
它使用浏览器中的History API来处理URL,创建一个像example.com/some/path这样真实的 URL 。
但是应用browserHistory,需要在服务器端做好配置,因为服务器会收到相应的请求。
hashHistory
Hash history 使用 URL 中的 hash(#)部分去创建形如 example.com/#/some/path 的路由。
Hash history 不需要服务器任何配置就可以运行。
createMemoryHistory
Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。
和另外两种history的一点不同是你必须创建它,这种方式便于测试。
const history = createMemoryHistory(location)