react-router是一个基于react的路由库,它可以让你向应用中快速的添加视图和数据流,同时保持页面与URL之间的同步。
import React from 'react'
import {render} from 'react-dom'
const About = React.createClass({...})
const Inbox = React.createClass({...})
const Home = React.createClass({...})
const App = React.createClass({
getInitialState() {
return {
route: window.location.hash.substr(1)
}
},
componentDidMount() {
window.addEventListener('hashchage', ()=> {
this.setState({
route: window.location.hash.substr(1)
})
})
},
render() {
let Child
switch (this.state.route) {
case '/about': Child = About; break;
case '/inbox': Child = Inbox; break;
default: Child = Home;
}
return (
<div>
<h1>App</h1>
<ul>
<li><a href="#/about">About</a></li>
<li><a href="#/inbox">Inbox</a></li>
</ul>
<Child/>
</div>
)
}
})
React.render(<App />, document.body)
如上,当URL的hash部分(指的是#后的部分)变化后,<App>会根据this.state.route来渲染不同的<Child>。现在看起来很直观,但是当你的路由结构复杂,项目变的比较庞大的时候,这种方法可能就不太适合了。
我们看下在使用react-router后的编码结构:
import React from 'react'
import { render } from 'react-dom'
// 首先我们需要导入一些组件...
import { Router, Route, Link } from 'react-router'
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
{/* 把 <a> 变成 <Link> */}
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{/*
接着用 `this.props.children` 替换 `<Child>`
router 会帮我们找到这个 children
*/}
{this.props.children}
</div>
)
}
})
// 最后,我们用一些 <Route> 来渲染 <Router>。
// 这些就是路由提供的我们想要的东西。
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox} />
</Route>
</Router>
), document.body)
看上面代码我们除去了对hash路由的判断,取而代之的是通过react-router控制了视图的显示。在内部,router会将你树级嵌套格式的<Route>转变成路由配置。我们也可以通过普通对象的方式来替代路由配置:
const routes = {
path: '/',
component: App,
childRoutes: [
{path: 'about', component: About},
{path: 'inbox', component: Inbox}
]
}
React.render(<Router routes={routes}>)
当渲染组件时,React Router会自动向Route组件中注入一些有用的信息,尤其是路径中动态部分的参数。
const Message = React.createClass({
componentDidMount() {
// 来自于路径 `/inbox/messages/:id`
const id = this.props.params.id
fetchMessage(id, function (err, message) {
this.setState({ message: message })
})
},
// ...
})
路由配置是一组指令,用来告诉router如何匹配URL以及匹配后如何执行代码。
import React from 'react'
import { Router, Route, Link } from 'react-router'
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>
{this.props.children}
</div>
)
}
})
const About = React.createClass({
render() {
return <h3>About</h3>
}
})
const Inbox = React.createClass({
render() {
return (
<div>
<h2>Inbox</h2>
{this.props.children || "Welcome to your Inbox"}
</div>
)
}
})
const Message = React.createClass({
render() {
return <h3>Message {this.props.params.id}</h3>
}
})
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 |
设想一下,当url为/时,我们想渲染一个在App中组件,不过此时App的render中的this.props.children还是undefined。这种情况下,可以使用IndexRoute来设置一个默认页面。
import { IndexRoute } from 'react-router'
const Dashboard = React.createClass({
render() {
return <div>Welcome to the app!</div>
}
})
React.render((
<Router>
<Route path="/" component={App}>
{/* 当 url 为/时渲染 Dashboard */}
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
此时,App的render中的this.props.children会将是<Dashboard>这个元素。
URL | 组件 |
---|---|
/ | App->Dashboard |
/about | App->About |
/inbox | App->Inbox |
/inbox/messages/:id | App->Inbox->Message |
如果我们可以将/inbox从/inbox/messages/:id中去除,并且能够让Message嵌套在App-Inbox。那我们可以通过绝对路径实现。
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)
绝对路径可能在动态路由中无法使用
如果改为/inbox/messages/5,这样都会匹配不到路径,会返回一个错误页面,我们可以通过<Redirect>使URL重新正常工作。
import { Redirect } from 'react-router'
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/message/5这个链接,他们会自动跳转到/message/5。
因为route一般被嵌套使用,所以使用JSX这种天然具有简洁嵌套型语法的结构来描它们的关系非常方便。我们也可以使用原生route数组对象。
const routeConfig = [
{
path: '/',
component: App,
indexRoute: {
component: Dashboard
},
childRoutes: [
{ path: 'about', component: About},
{ path: 'inbox', component: Inbox, childRoutes: [
{path: '/messages/:id', component: Message },
{path: 'message/:id', onEnter: function(nextState, replaceState) {
replaceState(null, '/messages/' + nextState.params.id)
}}
]}
]
}
]
路由由三个属性来决定是否匹配一个URL:
1. 嵌套关系
2. 路径语法
3. 优先级
React Router使用路由嵌套的概念让你定义view的嵌套集合,当一个URL调用时,整个路由集合中(匹配的部分)都会被渲染。嵌套路由被描述成一种属性结构,Reat-Router会深度优先遍历整个路由配置来寻找一个给定的URL相匹配的路由。
路由路径是匹配一个URL的一个字符串模式,大部分的路由路径都可以直接按照字面量理解,但是以下有几个特殊的符号:
1. :paramName -- 匹配一段位于/, ?或者#之后的URL.命中的部分将被作为一个参数。
2. () -- 在它内部的内容被认为是可选的
3. * --匹配任意字符串直到命中下一个字符或者整个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
路由算法会根据定义的顺序自顶向下匹配路由,因此,当你拥有两个兄弟路由节点匹配时,你必须确认前一个路由不会匹配后一个路由中的路径。
React Router时建立在history上的,简而言之,一个history知道如何去监听浏览器地址的变化,并解析这个URL转为location对象,然后router使用它匹配到路由,最后正确的渲染对应组件。
1. borwserHistory
2. hashHistory
3. createMemoryHistory
import {browserHistory} from 'react-router'
render(
<Router history={browserHistory} routes={routes} />,
document.getElementById('app')
)
Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path这样真实的 URL 。
Hash history 使用 URL 中的 hash(#)部分去创建形如 example.com/#/some/path 的路由。
首先,我们看下不使用默认路由的情形:
<Router>
<Route path="/" component={App}>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
当用户访问/时,App组件被渲染,但组件内的子元素却没有,App内部的this.props.children为undefined。你可以简单的使用{this.props.chidlren || ''}来渲染默认组件。
但现在,Home 无法参与到比如 onEnter hook 这些路由机制中来。 在 Home 的位置,渲染的是 Accounts 和 Statements。 由此,router 允许你使用 IndexRoute ,以使 Home 作为最高层级的路由出现.
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
现在 App 能够渲染 {this.props.children} 了, 我们也有了一个最高层级的路由,使 Home 可以参与进来。
如果你在这个 app 中使用 <Link to="/">Home</Link> , 它会一直处于激活状态,因为所有的 URL 的开头都是 / 。 这确实是个问题,因为我们仅仅希望在 Home 被渲染后,激活并链接到它。
如果需要在 Home 路由被渲染后才激活的指向 / 的链接,请使用 <IndexLink to="/">Home</IndexLink>
对于大型应用来说,一个首当其冲的问题就是所需加载的 JavaScript 的大小。程序应当只加载当前渲染页所需的 JavaScript。有些开发者将这种方式称之为“代码分拆” —— 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。
路由是个非常适于做代码分拆的地方:它的责任就是配置好每个 view。
React Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。在首次加载包中你只需要有一个路径定义,路由会自动解析剩下的路径。
Route 可以定义 getChildRoutes,getIndexRoute 和 getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React Router 会逐渐的匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。
文章来源:https://react-guide.github.io/react-router-cn/docs/guides/basics/RouteConfiguration.html