概念
“路由”是一个随处可见的东西,比如一座城里会有若干条道路通往若干个目的地,一个司机可以选择从哪条路到哪个地方。在单页面应用中会有若干个按钮或链接指向若干个组件或页面,有个叫路由器的东西就是用来设置路径和组件的对应关系的。
路由这个概念包含了三个部分:路径、目的地、路由器。
React Router
React项目的路由库是 React Router,它分为三个部分:
- react-router:核心组件
- react-router-dom:浏览器路由库
- react-router-native:native端路由库
核心库会随着两个端的库安装,所以在浏览器端只需要安装react-router-dom。
组件
根据路由的概念,React Router也提供了三大类组件:
-
Link:让一个元素指向某个路径,比如:
<Link to="/home"首页</Link
-
Route:指定路径和组件的对应关系,比如:
<Route path="/home" component={Home} /
-
Router:当链接被点击时,在若干个Route中寻找路径匹配的那一个,并渲染指定的组件
每类组件都针对不同的客户端提供了不同的组件,比如Router组件在浏览器端(其他端先不管)就提供了两种组件:<BrowserRouter>
、<HashRouter>
。
BrowserRouter 和 HashRouter
这两种组件分别是基于URL的pathname
段和hash
段的,很大的一个差别就是使用BrowserRouter时需要对服务器做一些设置,因为pathname会被发送到服务端。
一个简单应用
上图中黑框代表整个应用<App />
,由<Header />
、<Menu />
、<Content />
组成。<Menu />
中设置了三个菜单项,点击后分别将对应的组件渲染到<Content中 />
,而<Header />
则可以用来显示面包屑等组件。当前选中第三条菜单,渲染了<App3 />
组件。这个组件内部又有两个链接,分别指向不同的组件。
在这个应用中,路由发生了嵌套:
-
App
- /app1
- /app2
- /app3
- /app3-1
- /app3-2
路由配置
现在可以简单配置一下路由了,可以看到,不必把所有路由都放到一处。以下代码的,而各个组件的具体内容则简单放个标题也可以。
需要注意:
- 必须用Router组件包裹整个应用,可以用HashRouter或者BrowserRouter
- 可以在子组件内继续添加路由,但要记得把父组件所在的路由传递进去
//App.js
import React from "react";
import { HashRouter as Router, Route, Link, NavLink } from "react-router-dom";
import App1 from './App1'
import App2 from './App2'
import App3 from './App3'
import Header from './Header'
import Menu from './Menu'
import Content from './Content'
export default class MyApp extends React.Component{
render(){
return(
<Router basename="/myapp"
<div
<Header /
/* Menu组件下传了三个Link */
/* 像这样传进来的组件,在Menu内部通过props.children就可以访问 */
<Menu
<Link to="/app1"App1</Link
<Link to="/app2"App3</Link
<Link to="/app3"App3</Link
</Menu
</div
<Content
<Route path="/app1" component={App1} /
<Route path="/app2" component={App2} /
<Route path="/app3" render={()=(<App3 basePath="/app3" /)} /
</Content
</Router
)
}
}
/*
* App3内部又有路由配置
* 需要注意的是,主路由在App组件里,所以得把进入App3组件时已有的路由(取名basePath)传进来
*/
import { Route, Link } from "react-router-dom";
import Nav from './Nav'
import App3_1 from './App3-1'
import App3_2 from './App3-2'
export default const App3 = () = (
<div
<Nav
<Link to={this.props.basePath + "/app3-1"}App3-1</Link
<Link to={this.props.basePath + "/app3-2"}App3-2</Link
</Nav
<Route path={this.props.basePath + "/app3-1"} component={App3_1} /
<Route path={this.props.basePath + "/app3-2"} component={App3_2} /
</div
)
有了这个应用骨架,剩下的问题就好办了,接下来还需要考虑这几个问题:
1. 路由匹配
当一个<Link
被点击时,其to属性的值就被拿去和Route的path属性匹配。
路由其实就是目录层级结构的表示,Route就相当于对文件系统的描述,一个Route就表示了哪个路径下有哪个组件。
路由匹配就是拿Link的to属性值去看看它能够被哪个目录所包含,比如:有三个Route,分别是/
、/abc
和/abc/123
,显然从前到后是包含关系。此时如果有一个路径为/abc/123/xyz
的Link被点击,那么这三个Route都会匹配到。
那么问题来了,由于匹配到的Route都会被渲染,上面的结果就是点一个链接渲染了三个组件,而我们期望的是只渲染/abc/123/xyz
,我只知道两个解决方法:
/*-------------------- 方法1 -------------------*/
/* exact:让上级Route只匹配路径完全一样的Link */
<Route exact path="/" component={App1} / //只匹配“/”,不匹配“/...”
<Route exact path="/abc" component={App2} / //只匹配“/abc”,不匹配“/abc/...”
<Route path="/abc/123/xyz" component={App3} /
/*-------------------- 方法2 -------------------*/
/* switch:只渲染第一个匹配到的,此时要注意把上级目录的Route往下面放 */
<Switch
/* 虽然三个都匹配了,但Switch使得只有第一个被渲染 */
<Route path="/abc/123/xyz" component={App3} /
<Route path="/abc" component={App2} /
<Route path="/" component={App1} /
</Switch
2. NavLink
NavLink是特殊的Link,匹配到后,可以给它添加activeStyle或者activeClassName。
需要注意的是,匹配是Link和Route双方的事情,NavLink能够“感知”匹配从而改变自身样式,所以也要解决多个NavLink同时感知到匹配的问题,所以NavLink也需要使用exact,不然的话点击下级目录链接会导致所有上级目录Link变样式:
<NavLink exact to="/" activeStyle={{color: 'red'}}App1</NavLink
<NavLink exact to="/abc" activeStyle={{color: 'red'}}App1</NavLink
<NavLink exact to="/abc/123/xyz" activeStyle={{color: 'red'}}App1</NavLink
3. Route的三种渲染方式
-
component:给什么渲染什么
-
render:可以在渲染之前做点别的
<Route path="/" render={()={
console.log("额外的逻辑")
return <AppX /
}/
- children:可以根据是否匹配渲染不同组件,是否匹配可由自动传入的参数props.match获知
<Route path="/" children={(props)={
console.log("额外的逻辑")
return props.match ? <AppX : <AppY
}/
无论是否匹配到,children函数都会执行。不要在Switch中使用children!
4. 更多信息
一个路由被匹配到,其对应组件就会被传入三个props:match、location、history。
URL参数
Link中可以给路由传参:
<Route path="/app/:id" component={App1} /
<Link to="/app/:123456"To App1</Link
这个参数可以通过match.params.id拿到。