https://react-guide.github.io/react-router-cn/index.html
快速入门 简单路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import React from "react" ;import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom" ; function Home ( ) { return <h2 > Home</h2 > ; } function About ( ) { return <h2 > About</h2 > ; } function Users ( ) { return <h2 > Users</h2 > ; } export default function App ( ) { return ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/users">Users</Link> </li> </ul> </nav> {/* <Switch>检查它的子节点<Route>,并呈现与当前URL匹配的第一个子节点。 */} <Switch> <Route path="/about"><About /></Route> <Route path="/users"><Users /></Route> <Route path="/"><Home /></Route> </Switch> </div> </Router> ); }
嵌套路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 import React from "react" ;import { BrowserRouter as Router, Switch, Route, Link, useRouteMatch, useParams } from "react-router-dom" ; function Home ( ) { return <h2 > Home</h2 > ; } function About ( ) { return <h2 > About</h2 > ; } function Topics ( ) { let match = useRouteMatch(); return ( <div> <h2>Topics</h2> <ul> <li> <Link to={`${match.url}/components`}>Components</Link> </li> <li> <Link to={`${match.url}/props-v-state`}> Props v. State </Link> </li> </ul> {/* The Topics page has its own <Switch> with more routes that build on the /topics URL path. You can think of the 2nd <Route> here as an "index" page for all topics, or the page that is shown when no topic is selected */} <Switch> <Route path={`${match.path}/:topicId`}><Topic /></Route> <Route path={match.path}><h3>Please select a topic.</h3></Route> </Switch> </div> ); } function Topic() { let { topicId } = useParams(); return <h3>Requested topic ID: {topicId}</h3>; } export default function App() { return ( <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/topics">Topics</Link> </li> </ul> <Switch> <Route path="/about"><About /></Route> <Route path="/topics"><Topics /></Route> <Route path="/"><Home /></Route> </Switch> </div> </Router> ); }
路由配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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:
URL
组件
/
App
/about
App -> About
/inbox
App -> Inbox
/inbox/messages/:id
App -> Inbox -> Message
添加首页 想象一下当 URL 为 /
时,我们想渲染一个在 App
中的组件。不过在此时,App
的 render
中的 this.props.children
还是 undefined
。这种情况我们可以使用 IndexRoute
来设置一个默认页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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>
这个元素。这个功能类似 Apache 的DirectoryIndex
以及 nginx的 index
指令,上述功能都是在当请求的 URL 匹配某个目录时,允许你制定一个类似index.html
的入口文件。
我们的 sitemap 现在看起来如下:
URL
组件
/
App -> Dashboard
/about
App -> About
/inbox
App -> Inbox
/inbox/messages/:id
App -> Inbox -> Message
让 UI 从 URL 中解耦出来(绝对路径) 如果我们可以将 /inbox
从 /inbox/messages/:id
中去除,并且还能够让 Message
嵌套在 App -> Inbox
中渲染,那会非常赞。绝对路径可以让我们做到这一点。
1 2 3 4 5 6 7 8 9 10 11 12 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)
在多层嵌套路由中使用绝对路径的能力让我们对 URL 拥有绝对的掌控。我们无需在 URL 中添加更多的层级,从而可以使用更简洁的 URL。
我们现在的 URL 对应关系如下:
URL
组件
/
App -> Dashboard
/about
App -> About
/inbox
App -> Inbox
/messages/:id
App -> Inbox -> Message
兼容旧的 URL 等一下,我们刚刚改变了一个 URL! 这样不好 。 现在任何人访问 /inbox/messages/5
都会看到一个错误页面。:(
不要担心。我们可以使用 <Redirect>
使这个 URL 重新正常工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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/messages/5
这个链接,他们会被自动跳转到 /messages/5
。
进入和离开的Hook Route 可以定义 onEnter
和 onLeave
两个 hook ,这些hook会在页面跳转确认 时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证 或者在路由跳转前将一些数据持久化保存起来。
在路由跳转过程中,
继续我们上面的例子,如果一个用户点击链接,从 /messages/5
跳转到 /about
,下面是这些 hook 的执行顺序:
/messages/:id
的 onLeave
/inbox
的 onLeave
/about
的 onEnter
替换的配置方式 因为 route 一般被嵌套使用,所以使用 JSX 这种天然具有简洁嵌套型语法的结构来描述它们的关系非常方便。然而,如果你不想使用 JSX,也可以直接使用原生 route 数组对象。
上面我们讨论的路由配置可以被写成下面这个样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const routeConfig = [ { path : '/' , component : App, indexRoute : { component : Dashboard }, childRoutes : [ { path : 'about' , component : About }, { path : 'inbox' , component : Inbox, childRoutes : [ { path : '/messages/:id' , component : Message }, { path : 'messages/:id' , onEnter : function (nextState, replaceState ) { replaceState(null , '/messages/' + nextState.params.id) } } ] } ] } ] React.render(<Router routes ={routeConfig} /> , document .body)
路由匹配原理 路由 拥有三个属性来决定是否“匹配“一个 URL:
嵌套关系 和
它的 路径语法
它的 优先级
嵌套关系 React Router 使用路由嵌套的概念来让你定义 view 的嵌套集合,当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。嵌套路由被描述成一种树形结构。React Router 会深度优先遍历整个路由配置 来寻找一个与给定的 URL 相匹配的路由。
路径语法 路由路径是匹配一个(或一部分)URL 的 一个字符串模式 。大部分的路由路径都可以直接按照字面量理解,除了以下几个特殊的符号:
:paramName
– 匹配一段位于 /
、?
或 #
之后的 URL。 命中的部分将被作为一个参数
()
– 在它内部的内容被认为是可选的
*
– 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat
参数
1 2 3 <Route path="/hello/:name" > <Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan <Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
如果一个路由使用了相对路径
,那么完整的路径将由它的所有祖先节点的路径
和自身指定的相对路径
拼接而成。使用绝对路径
可以使路由匹配行为忽略嵌套关系。
优先级 最后,路由算法会根据定义的顺序自顶向下匹配路由。因此,当你拥有两个兄弟路由节点配置时,你必须确认前一个路由不会匹配后一个路由中的路径
。例如,千万不要 这么做:
1 2 <Route path="/comments" ... /> <Redirect from ="/comments" ... />
Histories React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location
对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
常用的 history 有三种形式, 但是你也可以使用 React Router 实现自定义的 history。
你可以从 React Router 中引入它们:
1 2 import { browserHistory } from 'react-router'
然后将它们传递给<Router>
:
1 2 3 4 render( <Router history ={browserHistory} routes ={routes} /> , document .getElementById('app' ) )
browserHistory
Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path
这样真实的 URL 。
服务器配置 服务器需要做好处理 URL 的准备。处理应用启动最初的 /
这样的请求应该没问题,但当用户来回跳转并在 /accounts/123
刷新时,服务器就会收到来自 /accounts/123
的请求,这时你需要处理这个 URL 并在响应中包含 JavaScript 应用代码。
一个 express 的应用可能看起来像这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const express = require ('express' )const path = require ('path' )const port = process.env.PORT || 8080 const app = express()app.use(express.static(__dirname + '/public' )) app.get('*' , function (request, response ) { response.sendFile(path.resolve(__dirname, 'public' , 'index.html' )) }) app.listen(port) console .log("server started on port " + port)
如果你的服务器是 nginx,请使用 try_files
指令 :
1 2 3 4 5 6 server { ... location / { try_files $uri /index.html } }
当在服务器上找不到其他文件时,这可以让 nginx 服务器提供静态文件服务并指向index.html
文件。
对于Apache服务器也有类似的方式,创建一个.htaccess
文件在你的文件根目录下:
1 2 3 4 5 RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L]
IE8, IE9 支持情况 如果我们能使用浏览器自带的 window.history
API,那么我们的特性就可以被浏览器所检测到。如果不能,那么任何调用跳转的应用就会导致 全页面刷新 ,它允许在构建应用和更新浏览器时会有一个更好的用户体验,但仍然支持的是旧版的。
你可能会想为什么我们不后退到 hash history,问题是这些 URL 是不确定的。如果一个访客在 hash history 和 browser history 上共享一个 URL,然后他们也共享同一个后退功能,最后我们会以产生笛卡尔积数量级的、无限多的 URL 而崩溃。
hashHistory
Hash history 使用 URL 中的 hash(#
)部分去创建形如 example.com/#/some/path
的路由。
我应该使用 createHashHistory
吗? Hash history 不需要服务器任何配置就可以运行,如果你刚刚入门,那就使用它吧。但是我们不推荐在实际线上环境中用到它,因为每一个 web 应用都应该渴望使用 browserHistory
。
像这样 ?_k=ckuvup
没用的在 URL 中是什么? 当一个 history 通过应用程序的 push
或 replace
跳转时,它可以在新的 location 中存储 “location state” 而不显示在 URL 中,这就像是在一个 HTML 中 post 的表单数据。
在 DOM API 中,这些 hash history 通过 window.location.hash = newHash
很简单地被用于跳转,且不用存储它们的location state。但我们想全部的 history 都能够使用location state,因此我们要为每一个 location 创建一个唯一的 key,并把它们的状态存储在 session storage 中。当访客点击“后退”和“前进”时,我们就会有一个机制去恢复这些 location state。
createMemoryHistory
Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。
和另外两种history的一点不同是你必须创建它,这种方式便于测试。
1 const history = createMemoryHistory(location)
实现示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React from 'react' import { render } from 'react-dom' import { browserHistory, Router, Route, IndexRoute } from 'react-router' import App from '../components/App' import Home from '../components/Home' import About from '../components/About' import Features from '../components/Features' render( <Router history ={browserHistory} > <Route path ='/' component ={App} > <IndexRoute component ={Home} /> <Route path ='about' component ={About} /> <Route path ='features' component ={Features} /> </Route > </Router > , document .getElementById('app' ) )
默认路由(IndexRoute)与 IndexLink 默认路由(IndexRoute) 在解释 默认路由(IndexRoute)
的用例之前,我们来设想一下,一个不使用默认路由的路由配置是什么样的:
1 2 3 4 5 6 <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.children ||
}` 来渲染一些默认的 UI 组件。
但现在,Home
无法参与到比如 onEnter
hook 这些路由机制中来。 在 Home
的位置,渲染的是 Accounts
和 Statements
。 由此,router 允许你使用 IndexRoute
,以使 Home
作为最高层级的路由出现.
1 2 3 4 5 6 7 <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
可以参与进来。
Index Links 如果你在这个 app 中使用 <Link to="/">Home</Link>
, 它会一直处于激活状态,因为所有的 URL 的开头都是 /
。 这确实是个问题,因为我们仅仅希望在 Home
被渲染后,激活并链接到它。
如果需要在 Home
路由被渲染后才激活的指向 /
的链接,请使用 <IndexLink to="/">Home</IndexLink>