1. React Router
React Router包含了四个包:
包名 | 描述 |
---|---|
react-router | React Router 核心api |
react-router-dom | React Router的DOM绑定,在浏览器中运行不需要额外安装 react-router |
react-router-native | React Native中使用,而实际的应用中,其实不会使用这个。 |
react-router-config | 静态路由的配置 |
主要使用 react-router-dom
2. React Router基本原理
React Router 甚至大部分的前端路由都是依赖于 history.js 的,它是一个独立的第三方js库。可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的 API。
//默认使用的是history模式
import { BrowserRouter as Router} from 'react-router-dom';
//更改为hash模式
import { HashRouter as Router} from 'react-router-dom';
- 老浏览器的 history: 通过
hash
来存储在不同状态下的history
信息,对应createHashHistory
,通过检测location.hash
的值的变化,使用location.replace
方法来实现 url 跳转。通过注册监听window
对象上的hashChange
事件来监听路由的变化,实现历史记录的回退。 - 高版本浏览器: 利用 HTML5 里面的 history,对应
createBrowserHistory
,使用包括pushState
,replaceState
方法来进行跳转。通过注册监听window
对象上的popstate
事件来监听路由的变化,实现历史记录的回退。 - node环境下: 在内存中进行历史记录的存储,对应
createMemoryHistory
。直接在内存里push
和pop
状态。
3. 使用
在终端中执行下面的命令,安装 react-router-dom,-S 表示在生产环境使用
yarn add react-router-dom -S
3.1 基本使用
- <Link> 默认会被解析成
a标签
,相当于vue中的 <router-link>; - <Route>标签中,渲染组件可以使用
component 属性
,也可以使用render() 函数
,使用 component 的方式是不能直接在组件上添加属性的,可以使用 render() 函数,这个常用于页面组件级别的权限管理; - 函数组件和 render() 函数默认接收一个
props参数
,通过 props.match.params 可以获取 url的动态参数; <Route>
标签中,exact
属性表示严格匹配,为 false 时表示模糊匹配;<Redirect>
标签,通过设置 from 和 to 属性值,进行路由跳转;<Switch>
标签的作用是排他性,从上往下按顺序匹配路由,匹配成功后,就不再往下找了;- 路由传参,有
动态参数(如 /:id)
和params 参数(如 /name=zz&age=18)
两种方式,可以通过props
属性来获取传递的内容; 多视图概念
,如果不使用<Switch>标签的话,所有匹配成功的路由都会显示其渲染的内容;嵌套路由
,在子组件中路由使用方法和父组件中一致,不同的是子组件中最外层不需要再使用<Router>标签;- 404页面通常配置在最下面,<Route>标签中 path 属性值为"
*
",也可以不写 path,表示全部匹配;
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import {
BrowserRouter as Router, //设置别名为 Router
Route,
Link,
Redirect,
Switch,
useLocation
} from 'react-router-dom'
const Home = () => {
return <h1>Home page</h1>
}
const About = (props) => {
//函数组件默认接收一个 props参数
//以下三种方式都可以获取 url中的参数
let query1=props.location.search;
let query2=useLocation().search;
let query3=window.location.search;
//打印结果都为 ?city=beijing&code=18
console.log(query1,query2,query3);
//通过 new URLSearchParams()获取参数的值
let query=new URLSearchParams(useLocation().search).get('city')
return <h1>About page,city:{query}</h1>
}
const Login = () => {
return <h1>Login page</h1>
}
class Page extends Component {
render() {
return (
<Router>
{/* Link 默认会被解析成 a标签 */}
<Link to="/home">首页</Link>
<Link to="/about/?city=beijing&code=18">关于我们</Link>
<Link to="/product/34">产品</Link>
<Link to="/login">登录</Link>
<hr />
{/* Switch 的作用是从上往下匹配路由,匹配成功后,就不再往下找了 */}
<Switch>
{/* 渲染组件可以使用 component,也可以使用 render()函数 */}
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/login" component={Login}></Route>
<Route path="/product/:id" render={(props)=>{
//默认接收一个props参数,通过 props.match.params获取 url的动态参数
return <h1>Product page,id:{props.match.params.id}</h1>
}}></Route>
{/* exact 表示精确匹配 */}
<Redirect exact from="/" to="/home"></Redirect>
<Route path="*" render={() => {
return <div>404 page not found...</div>
}}></Route>
</Switch>
</Router>
)
}
}
ReactDOM.render(
<Page/>,
document.getElementById('root')
);
3.2 路由权限验证
在 vue 中,我们可以使用 router.beforeEach
做路由的权限验证,在 react 中,可以通过 render() 函数做逻辑处理
,来实现这个功能。
例如,当用户是未登录状态时,可以访问首页内容,当访问用户管理页面时,跳转至登陆页,代码如下:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
const Home = () => <h1>Home page</h1>
const Login = () => <h1>Login page</h1>
const User = () => <h1>user page</h1>
export default class Page extends Component {
constructor() {
super();
//routers 相当于是路由配置文件
this.routers = [
{path: "/home", component: Home, auth: false},
{path: "/login", component: Login, auth: false},
{path: "/user", component: User, auth: true}
]
}
render() {
return (
<Router>
<Link to="/home">首页</Link>
<Link to="/user">用户管理</Link>
{
this.routers.map((item, index) => {
return <Route key={index} path={item.path} render={() => {
//判断当前跳转的路由是否需要进行验证
return item.auth ? <Login></Login> : <item.component></item.component>
}}>
</Route>
})
}
</Router>
)
}
}
ReactDOM.render(
<Page/>,
document.getElementById('root')
);
3.3 设置路由选中状态
通过设置 react-router-dom 中自带的 <NavLink>
标签的 activeClassName
属性,可以动态设置当前页面路由标签的选中样式;也可以通过下面自定义导航
的方式实现当前页面路由标签的选中样式。
<NavLink activeClassName="active" to="/home">首页</NavLink>
<NavLink activeClassName="active" to="/about">关于我们</NavLink>
3.4 自定义导航
在 react-router-dom 中,<Link>
标签默认会被解析成 a 标签,如果想以 <li>
标签或者别的标签展示,就可以通过自定义导航来实现;自定义导航也可以设置当前页面路由标签的选中样式。设置自定义导航有以下几种方式:
3.4.1 方式一
创建自定义组件,通过将<Link>标签包裹,完成自定义导航。
const CustomLink = (prop) => {
return (
<li>
<Link to={prop.path}>{prop.name}</Link>
</li>
)
}
<CustomLink path="/home" name="首页"></CustomLink>
3.4.2 方式二
通过 withRouter()
高阶函数,可以拿到路由的上下文信息,并对当前组件功能做增强,这种方式用起来更灵活,藕合性低。
通过 this.props.history.push()
可以实现编程式导航页面跳转。
/* About.jsx */
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class About extends Component {
handleClick = () => {
//编程式导航
this.props.history.push(this.props.path)
}
render() {
return (
<li className={this.props.path === this.props.location.pathname ? 'active':''} onClick={this.handleClick}>
关于我们
</li>
)
}
}
export default withRouter(About)
在入口文件中引用:
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import About from './About'
const Index=()=>{
return (
<Router>
<About path="/about"></About>
</Router>
)
}
ReactDOM.render(
<Index/>,
document.getElementById('root')
);
注意:
- withRouter() 只能作在类组件中。
- withRouter() 使用时,需要放在<Router>标签内,否则会报下面的错。
3.4.3 方式三
- 通过 <Route> 标签的
children
属性,可以进行逻辑操作,将<Link>标签包裹,以及用来判断当前标签是否被选中。 - children() 用法和 render() 类似。
- 不管是什么路由匹配,只要点击路由标签,children 里面的函数都会被执行。
- 如果 <Route> 标签上有 children 属性,则 props.match 有值,否则 props.match 为 null。
<Route path="/product" children={(props)=>{
return <Link className={props.match?'active':''} to="/product">产品</Link>
}}></Route>
自定义导航三种方式汇总:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router,Route,Link,NavLink} from 'react-router-dom'
import About from './About' //About.jsx 文件同上面3.4.2中
const CustomLink = (props) => {
return <li><Link to={props.path}>{props.name}</Link></li>
}
class Page extends Component {
render() {
return (
<Router>
<ul>
//方式一
<CustomLink path="/home" name="首页"></CustomLink>
//react-router-dom 自带标签
<NavLink activeClassName="active" to="/login">登录</NavLink>
//方式二
<About path="/about"></About>
//方式三
<Route path="/product" children={(props)=>{
return <Link className={props.match?'active':''} to="/product">产品</Link>
}}></Route>
</ul><hr/>
<Route exact path="/home"><h1>首页</h1></Route>
<Route exact path="/login"><h1>登录</h1></Route>
<Route exact path="/about"><h1>关于我们</h1></Route>
<Route exact path="/product"><h1>产品</h1></Route>
</Router>
)
}
}
ReactDOM.render(
<Page/>,
document.getElementById('root')
);
页面效果如下,点击上面的导航,在下方显示对应内容,并且该导航标签上增加 active 的样式(这里设置选中导航字体为红色)。