原文链接:https://react-guide.github.io...
react-router是一个基于react的路由库,它可以让你向应用中快速的添加视图和数据流,同时保持页面与URL之间的同步。
路由配置
1. 不使用React-Router的情形
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}>)
2. 获取URL参数
当渲染组件时,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 }) }) },// ... })
3. 路由配置
路由配置是一组指令,用来告诉router如何匹配URL以及匹配后如何执行代码。
import React from 'react' import { Router,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 |
4. 添加首页
设想一下,当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 |
5. 让UI从URL中解耦出来
如果我们可以将/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)
绝对路径可能在动态路由中无法使用
6. 兼容旧的URL
如果改为/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。
7. 替换的配置方式
因为route一般被嵌套使用,所以使用JSX这种天然具有简洁嵌套型语法的结构来描它们的关系非常方便。我们也可以使用原生route数组对象。
const routeConfig = [ { path: '/',indexRoute: { component: Dashboard },childRoutes: [ { path: '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. 优先级
1. 嵌套关系
React Router使用路由嵌套的概念让你定义view的嵌套集合,当一个URL调用时,整个路由集合中(匹配的部分)都会被渲染。嵌套路由被描述成一种属性结构,Reat-Router会深度优先遍历整个路由配置来寻找一个给定的URL相匹配的路由。
2. 路径语法
路由路径是匹配一个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
3. 优先级
路由算法会根据定义的顺序自顶向下匹配路由,因此,当你拥有两个兄弟路由节点匹配时,你必须确认前一个路由不会匹配后一个路由中的路径。
Histories
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') )
1. browserHistory
Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path这样真实的 URL 。
2. hashHistory
Hash history 使用 URL 中的 hash(#)部分去创建形如 example.com/#/some/path 的路由。
默认路由(IndexRoute)与 IndexLink
1. 默认路由
首先,我们看下不使用默认路由的情形:
<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 可以参与进来。
2. Index Links
如果你在这个 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 对应页面所需的路径配置和组件。