前端路由就是把不同路由对应不同的内容或页面对应展示出来。路由可以帮助我们很好的管理页面和代码,增强用户的体验感。路由模式有hash和 history 两种模式,通常用hash模式。在react中,react-router是跨平台的,内置通用组件和通用Hooks。react-router-dom是在react-router基础上提供了Link和NavLink,而且依赖history库提供了两个浏览器端适用的BrowserRouter和HashRouter,现在的项目基本都是使用react-router-dom。本篇文章介绍了使用频率高和我接触到的路由内容。
一、路由原理
通俗的来说,路由用来管理url地址和视图之间的关系,一个地址对应一个页面要展示的内容,下面展示的地址就是hash模式(/#/)
原理:
1、准备视图(html)
2、准备路由的路线(可以是一个对象,键名是路线名称和值是视图地址)
3、通过hash地址的路线,获取“视图地址”
4、在指定标签中,加载需要的视图页面
原理图:灵魂画手
二、使用
(一)安装
npm i react-router-dom@5
(二)导入
import { ...通用组件 } from 'react-router-dom'
(三)使用
<BrowserRouter >
<Route path="路径" component={组件}></Route>
</BrowserRouter >
三、通用组件
(一)BrowserRouter
history模式,页面跳转原理是使用了pushState、replaceState。使用这个包裹,路由的形式将以/展示,可以通过as重命名,一般见到的形式为:BrowserRouter as Router(把BrowserRouter 重命名为Router )
属性:
basename(string):所有位置的基本url,应用是从服务器上的子目录提供的,则需要将其设置为子目录,格式正确的基本名称应以斜杆开头,但不能以斜杆结尾
getUserConfirmation(fn):用于确认导航的功能,默认使用window.confirm
<BrowserRouter>
<Route path='/films' component={Films}></Route>
<Route path='/home' component={Home}></Route>
</BrowserRouter>
(二)HashRouter
hash模式,页面跳转原理是使用了location.hash、location.replace。使用这个包裹,路由的形式将以/#/展示,虽然长的丑,但是用的比较多
<HashRouter>
<Route path='/films' component={Films}></Route>
<Route path='/home' component={Home}></Route>
</HashRouter>
注意:BrowserRouter和HashRouter的区别
1、底层原理不一样
HashRouter使用的是Url的哈希值
BrowserRouter使用的是h5的history api,不兼容ie9及以下版本
2、path表现形式不一样HashRouter带个#号
3、HashRouter刷新后对路由state参数的影响,而BrowserRouter是将state保存再history对象中
4、HashRouter可以用于解决一些路径错误相关的问题
(三)Route
匹配组件,并展示组件。即匹配成功后,组件立即被替换成匹配的组件
属性 | 类型 | 作用 |
path | str | obj | 路由匹配路径,没有path属性的Route总是会匹配 |
exact | bool | 为true时,要求全路径匹配(/home)。路由默认为“包含”的(/和/home)都匹配,这意味着多个Route可以同时进行匹配和渲染 |
component | fn | component | 在地址匹配的时候React的组件才会被渲染,route props也会随着一起被渲染 |
render | func | 内联渲染和包装组件,要求要返回目标组件的使用 |
<Route path='/films' component={Films} />
或者
<Route path="/" exact render={() => {
return <IndexPage user={user} />
}} />
注意:模糊匹配和精准匹配
因为exact的存在,分为模糊匹配和精准匹配
(四)Switch
排他性(单一)匹配。如果不想使用包容性,那么使用Switch,类似于js中的switch分支语句,被包裹在里面的组件会被匹配到一个,并跳出结束,常用于侧边栏,引导选项卡
属性 | 类型 | 作用 |
location | str | obj | 一般我们不会给该组件设置 location 属性。 不设置: Switch组件的子组件(一般是 Route 或 Redirect)会根据当前浏览器的 location 作为匹配依据來进行路由匹配。 |
<HashRouter>
{/* Switch类似于Js里面的Switch分支,被包裹的相当于case,总能匹配到一个,并跳出 */}
{/* 这样能做到一个页面出出现一个,不会多个页面展示在一个页面中 */}
<Switch>
<Route path='/films' component={Films}></Route>
<Route path='/home' component={Home}></Route>
</Switch>
</HashRouter>
(五)Redirect
重定向,如用户输入了/,见到/会指定跳转到重新定义的页面,在这里使用了exact,功能精准匹配,只有看到单独是/的才会重定向。模糊匹配,只要含有/就会响应
属性 | 类型 | 作用 |
from | string | 来自 |
to | string object | 去向 |
push | boolean | 添加历史记录 |
exact | boolean | 严格匹配 |
sensitive | boolean | 区分大小写 |
<HashRouter>
<Route path='/films' component={Films}></Route>
<Route path='/home' component={Home}></Route>
{/* 模糊匹配 */}
{/* <Redirect from='/' to='/home'/> */}
{/* 精准匹配 */}
<Redirect from='/' to='/home' exact/>
</HashRouter>
温馨提示:如果没有子路由的情况,建议大家配都加一个exact
;如果有子路由,建议在子路由中加exact
,父路由不加; 而strict
是针对是否有反斜杠的,一般可以忽略不配置
(六)Link
相当于a标签,在react将虚拟DOM渲染成真实DOM后,Link组件也被渲染成了a标签,常用于导航
属性 | 类型 | 作用 |
to | str | obj{pathname:,search:,hash:} | 要跳转的路径或地址 |
replace | bool | 是否替换历史记录 |
<ul>
<li>
<Link to='/films'>电影</NavLink>
</li>
<li>
<Link to='/cimans'>影院</NavLink>
</li>
<li>
<Link to='/home'>我的</NavLink>
</li>
</ul>
(七)NavLink
NavLink方式相当于Link的加强版
属性 | 类型 | 作用 |
to | str | obj{pathname:,search:,hash:} | 要跳转的路径或地址 |
replace | bool | 是否替换历史记录 |
activeClassName | str | 设置选中样式,默认值为active |
activeStyle | obj | 当元素被选中时,为此元素添加样式 |
exact | bool | 为true时,只有当导致和完全匹配class和style才会应用 |
strict | bool | 为true时,在确定为位置是否与当前URL匹配时,将考虑位置pathname后的斜线 |
isActive | fun | 判断链接是否激活的额外逻辑的功能 |
<ul>
<li>
<NavLink to='/films'>电影</NavLink>
</li>
<li>
<NavLink to='/cimans'>影院</NavLink>
</li>
<li>
<NavLink to='/home'>我的</NavLink>
</li>
</ul>
(八)withRouter
将一个组件包裹进Route
里面, 然后react-router
的三个对象history, location, match
就会被放进这个组件的props
属性中.
// withRouter实现原理:
// 将组件包裹进 Route, 然后返回
// const withRouter = () => {
// return () => {
// return <Route component={Nav} />
// }
// }
// 这里是简化版
const withRouter = ( Component ) => () => <Route component={ Component }/>
//withRouter的返回值是一个新组件
如果我们某个东西不是一个Router
, 但是我们要依靠它去进行浏览记录的前进后退 这时候就可以使用withRouter
,将一般组件变成路由组件。使用场景:比如点击页面的logo, 返回首页, 这时候就可以使用withRouter来做
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom' //引入withRouter
class Header extends Component {
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
console.log('Header组件收到的props是',this.props);
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是一个新组件
三、通用Hooks
使用在5.1版本及之后,并且是函数式组件中才可以使用,使用见第四点传参时候的代码演示
(一)useHistory
获取History对象。访问history对象,进行编程式的导航
//导入
import {useHistory } from "react-router-dom"
const Home = () => {
return (
<div>Home</div>
)
}
const Detail = () => {//使用
const history = useHistory()
console.log("history",history);//获取History对象
console.log("获取当前页路径",history.location.pathname);//获取当前页路径
//和下面通过js获取一致
console.log(props.location.pathname);
return (
<div><-- 使用-->
<button onClick={() => { history.push('/')}}>go home</button>
</div>
)
}
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/detail/:id" component={Detail}/>
</Switch>
</Router>
</div>
);
}
(二)useLocation
获取Location对象。在各层组件中,轻松获取location对象。在V5.1版本之前,我们需要使用props.location。而对于更深层的组件,还需要使用withRouter
//导入
import {
useLocation
} from "react-router-dom";
//使用
const location = useLocation ()
console.log("location",location);
(三)useParams
获取路由参数的键值对的对象,访问当前的<Route>的match.params。在V5.1版本之前,我们需要通过props.match
获取路由参数。对于更深层的组件还需要使用高阶组件withRouter
//设置<Route>的match.params
<Router>
<Switch>
<Route path="/:path">
<Home></Home>
</Route>
</Switch>
</Router>//获取
let { path } = useParams();
console.log("path ",path );
(四)useRouteMatch
useRouteMatch
,接受一个path
字符串作为参数。当参数的path
与当前的路径相匹配时,useRouteMatch会返回match对象,否则返回null。
useRouteMatch
在对于一些,不是路由级别的组件。但是组件自身的显隐却和当前路径相关的组件时,非常有用。
比如,你在做一个后台管理系统时,网页的Header只会在登录页显示,登录完成后不需要显示,这种场景下就可以用到useRouteMatch
。
const Home = () => {
return (
<div>Home</div>
)
}
// Header组件只会在匹配`/detail/:id`时出现
const Header = () => {
// 只有当前路径匹配`/detail/:id`时,match不为null
const match = useRouteMatch('/detail/:id')
return (
match && <div>Header</div>
)
}
const Detail = () => {
return (
<div>Detail</div>
)
}
function App() {
return (
<div className="App">
<Router>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/detail/:id" component={Detail}/>
</Switch>
</Router>
</div>
);
}
(五)useRoutes
路由表,钩子在useRoutes
功能上等同于<Routes>,但它里面使用 JavaScript 对象
而不是<Route>
元素来定义您的路由
-
useRoutes()
的参数为数组。 -
useRoutes
的返回值要么是可用于渲染路由树的有效 React 元素,要么null
是不匹配的元素。 -
嵌套路由用 children 实现。
使用useRoutes实现:
import * as React from "react";
import { useRoutes } from "react-router-dom";function App() {
let element = useRoutes([
{
path: "/",
element: <Dashboard />,
children: [
{
path: "messages",
element: <DashboardMessages />,
},
{ path: "tasks", element: <DashboardTasks /> },
],
},
{ path: "team", element: <AboutPage /> },
]);return element;
}
使用Routes和Route实现:
<Routes>
<Route path="/" element={<Dashboard />}>
<Route
path="messages"
element={<DashboardMessages />}
/>
<Route path="tasks" element={<DashboardTasks />} />
</Route>
<Route path="about" element={<AboutPage />} />
</Routes>
四、路由传参
在v5.x中只有编程式的获取参数方式,在v5.1及之后增加了Hooks后,既可以用编程式的方式获取url携带的参数,也可以使用Hooks中的方法来获取(只能是函数式组件),前面一种也成为显式传参,后面两种成为隐式传参
(一)params参数
1、注册路由
//注册路由
<Route path="/details/:name/:age" component={Details}/>
2、路由跳转
//路由跳转
<Link to='/details/tom/18'}>详情</Link>
或
<button onClick={()=>{props.history.push('details/tom/18')}}>去详情页</button>
3、在详情页获取参数
//在详情页获取参数
console.log(props.match.params);
或
import { useParams } from "react-router-dom";
const params = useParams();
(二)search参数
1、注册路由
//注册路由
<Route path="/details" component={Details}/>
2、路由跳转
//路由跳转
<Link to='/details/tom/18'}>详情</Link>
或
<button onClick={()=>{props.history.push('details?name=tom&age=18')}}>去详情页</button>
3、在详情页获取参数
//在详情页获取参数
console.log(props.location.search);//拿到的是?id=1&age=18,
或
import { useParams } from "react-router-dom";
const params = useParams();拿到的是?id=1&age=18
在以上两种方式中,获取到的search是urlencoded编码字符串,需要借助query-string解析参数成对象或者自己封装一个截取的工具函数
const { search } = props.location
const { num } = qs.parse(search.slice(1))
(三)state参数
1、注册路由
//注册路由
<Route path="/details" component={Details}/>
2、路由跳转
//路由跳转
<Link to={ {
pathname:'/detils',
state:{
id:1,
age:18
}
}} > 去详情页 </Link>
或
<button onClick={()=>{props.history.push({
pathname:'/details',
state:{
id:1,
age:18
}
})}}>去详情页</button>
3、在详情页获取参数
//在详情页获取参数
console.log(props.history.location.state);//{id: 1, age: 18}
import { useLocation } from "react-router-dom";
const { state } = useLocation();
console.log(state);//{id: 1, age: 18}