1.路由介绍
使用 react 构建的单页面应用,想要实现页面间的跳转,想要随着 url 地址的切换显示不同的内容,就是使用路由。在 react 中有两个包可以实现这个需求,react-router 和 react-router-dom。
react-router:实现了路由的核心功能。
react-router-dom:基于 react-router 加入了在浏览器运行环境下的一些功能。分为v5版本和v6版本,下面将介绍两种版本的用法。
2.路由的使用 v5版本
2.1 安装
npm i react-router-dom@5
2.2 定义路由规则
在使用之前先要用 react-router-dom 提供的两个组件 BrowserRouter或者HashRouter 包裹跟组件。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter,HashRouter } from 'react-router-dom';
// BrowserRouter,HashRouter 是实现的是history和hash两种路由模式
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<BrowserRouter>
//<HashRouter>
<App />
//</HashRouter>
</BrowserRouter>
// </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
通过路由组件来定义路由规则:
Switch 使用Switch组件包裹路由规则,匹配到路由之后就不继续往下匹配了。
Route 设置路由(path路径,component组件)。通过将path设置为 * 来添加404路由。
Redirect 设置路由重定向,可以添加exact,让路由严格匹配(当路由地址与路径全部匹配时才渲染页面)。
二级路由的定义:
二级路由的定义和一级路由一样(路由规则直接定义在组件内部,同时路由规则也充当了路由的渲染出口)。
注意:当某一个以及路由存在嵌套的子路由时,当前所在的以及路由规则就不能添加exact严格匹配,不然会无法渲染。
//路由定义代码演示组件懒加载以及组件渲染模式
//一级路由
import './App.css';
import React, { Component, lazy, Suspense } from 'react';
import { Route, Redirect, Switch } from 'react-router-dom'
/**
* react 中通过 react-router 喝 react-router-dom 实现路由跳转,通过url地址的变化切换不同的内容
* ------一级路由
* 1.导入 Route 组件 (设置一级路由 path路径 component组件) //需要使用Reuter去包裹路由组件 <BrowserRouter/>
* 2.导入 Redirect 组件 (路由重定向 默认打开的页面) 要让路由变成严格匹配 exact (当页面路径与路由路径全部都相等时才匹配)
* 3.通过设置 path="*" component={NotFount} 来设置404页面
* 4.Switch 组件 (Switch组件包裹Route组件,匹配到第一个就不再往下匹配)
* ------二级路由
* 1.在一级路由组件内定义二级路由
* 2.二级路由组件内定义二级路由
*/
/**
* 组件懒加载:
* 在react中导入一个 lazy 函数 ,lazy函数返回一个函数,该函数返回一个React组件
* 在导入一个 Suspense 组件,包裹懒加载的组件,当组件加载完成之前,显示一个加载中的组件 (在组件在懒加载创建的时候显示一些东西,不至于让他报错)
*
* 懒加载对打包的影响:
* 懒加载之后会打包都各自的js文件,如果没处理的话会默认打包到一个js文件中
*/
/**
* 路由渲染的三种方式
* component :组件渲染
* component:传入一个回调函数,也需要将props传入进去
* render:函数渲染 (函数返回一个组件) 不过他返回的是一个全新的组件,需要将props传入进去
*/
// 导入组件
// import Index from './view/Index.jsx'
// import Login from './view/Login.jsx'
// import NotFount from './view/NotFount.jsx'
// 懒加载组件
let Index = lazy(() => import('./view/Index.jsx'))
let Login = lazy(() => import('./view/Login.jsx'))
let NotFount = lazy(() => import('./view/NotFount.jsx'))
class App extends Component {
render() {
return (
<div className='app'>
<Suspense fallback={<div>加载中...</div>}> {/* fallback 属性设置加载中的组件 */}
<Switch>
{/*Route组件本身也充当一个路由出口 */}
{/* 注意:当某个组件内存在嵌套子路由时,当前路由所在的路由规则就不能添加exact精确匹配属性,不然就会无法渲染 */}
{/* <Route exact path="/index" component={Index} /> 一级路由 */}
<Route path="/index" component={Index} />
{/* <Route path="/login" component={Login} /> */}
{/* <Route path="/login" component={(props) => {
return <Login {...props} />
}} /> */}
<Route path="/login" render={(props) => {
return <Login {...props} />
}} />
<Redirect exact from="/" to="/index" /> {/* 路由重定向 默认打开的页面 */}
<Route path="*" component={NotFount} /> {/* 404页面 */}
</Switch>
</Suspense>
</div>
);
}
}
export default App;
//二级路由
import React, { Component } from 'react'
import { NavLink, Route, Switch, Redirect } from 'react-router-dom'
import './Index.css'
// 导入组件
import Home from './Index/Home'
import Mine from './Index/Mine'
import NotFount from '../view/NotFount'
export default class Index extends Component {
render() {
return (
<div className='index'>
{/* 定义二级路由规则,也会充当渲染出口 */}
<Switch>
<Route path="/index/home" component={Home} />
{/* 动态路由传参 */}
{/* <Route path="/index/mine/:id" component={Mine} /> */}
<Route path="/index/mine" component={Mine} />
<Redirect from="/index" to="/index/home" />
<Route path="*" component={NotFount} />
</Switch>
</div>
)
}
}
2.3 跳路由及路由传参
跳路由
声明式导航: Link(组件最终会渲染成一个a标签),NavLink(组件最终会渲染成一个标签,会自动维护active类的添加和删除)
编程式导航:
通过当前组件实例可以获取,histroy,location,match等属性。
histroy:可以使用其中的push,relpace,goBack,go等方法来跳路由。
location:pathname,search,state等属性来获取路由信息。
match:params,path,url等属性来获取路由信息。
//声明式导航
<NavLink to="/index/home">
<span className='iconfont icon-home'></span>
<div className="name">店铺</div>
</NavLink>
<NavLink to="/index/mine">
<div className="name">个人中心</div>
</NavLink>
//编程式导航
// 跳转到个人中心
toMine() {
// 通过js代码跳路由
// this.props.history.push('/index/mine')
// this.props.history.push({ pathname: `/index/mine/${3}` }) //写成对象可以传参 动态路由传参
//query传参
// this.props.history.push({ pathname: '/index/mine', query: { id: 3 } })
//state传参
this.props.history.push({ pathname: '/index/mine', state: { id: 3 } })
this.refs.mine.classList.add('active')
}
<div ref='mine' className="active" onClick={() => { this.toMine() }}>个人中心</div>
路由传参以及接收路由参数
/**
* -----路由传参
* 1.动态路由传参
* 路由地址中带有 :xx 这样得路由地址就是动态路由(刷新页面参数不会丢失,参数显示在地址栏)
* <Route path="/index/home/:id" component={Home} />
* 跳路由 this.props.history.push('/index/home/3')
* 组件内通过 props.match.params.xx 接收参数
*
* 2.通过query传参
* 路由地址正常 (刷新页面参数会丢失,参数不显示在地址栏)
* 跳路由 props.history.push({ pathname: '/index/mine', query: { id: 3 } })
* 组件内通过 props.location.query 获取参数
*
* 3.通过state传参
* 路由地址正常 (刷新页面参数不会丢失,参数不显示在地址栏)
* 跳路由 props.history.push({ pathname: '/index/mine', state: { id: 3 } })
* 组件内通过 props.location.state 获取参数
*/
//跳路由
// 跳转到个人中心
toMine() {
// 通过js代码跳路由
// this.props.history.push('/index/mine')
// this.props.history.push({ pathname: `/index/mine/${3}` }) //写成对象可以传参 动态路由传参
//query传参
// this.props.history.push({ pathname: '/index/mine', query: { id: 3 } })
//state传参
this.props.history.push({ pathname: '/index/mine', state: { id: 3 } })
this.refs.mine.classList.add('active')
}
//接收参数
componentDidMount() {
// 接收动态路由传递过来的参数
console.log(this.props)
// console.log(this.props.match.params.id)
// 接收query传递的参数
// console.log(this.props.location.query)
// 接收state传递的参数
console.log(this.props.location.state)
}
注意: 任何一个组件,只要是通过路由规则匹配渲染的,都会嵌入 history,location,match等属性。如果没有可以使用 withRouter 高阶组件去处理一下就可以了。
3.路由的使用 v6版本
3.1 安装
npm i react-router-dom@6
3.2 定义路由规则
v5跟v6区别:
- 在v6中Route 中引入组件,不再使用component属性,而是element属性,而且组件不能跟以前一样直接放进去,得放成标签的样子。
- 在v6中Switch 成了 Routes。
- 在v6中Redirect 成了 Navigate。
- 在v6中Routes 内部只能嵌套 Route或 Fragment。
- 在v6中路由定义 支持嵌套定义,不像v5,要分散在各个组件中去定义。
- 在v6中,如果某个路由规则下有二级路由,则要在该路由规则对应的组件下放置一个 Outlet 组件,作为路由渲染的出口。
v6路由也需要用路由管理组件去包裹路由规则。
import './App.css';
import React from 'react';
import { Route, Navigate, Routes } from 'react-router-dom'
// 在v6中
// 在v6中Route 中引入组件,不再使用component属性,而是element属性
// 在v6中Switch 成了 Routes
// 在v6中Redirect 成了 Navigate
// 在v6中Routes 内部只能嵌套 Route或 Fragment
// 在v6中路由定义 支持嵌套定义,不想v5,要分散在各个组件中去定义
// 在v6中,如果某个路由规则下有二级路由,则要在改路由规则对应的组件下放置一个 Outlet 组件,作为路由渲染的出口
// 一级路由组件
import Index from './components/Index'
import Login from './components/Login'
// 二级路由组件
import Home from './components/Index/Home'
import Mine from './components/Index/Mine'
function App(props) {
return (
<div className='app'>
{/* 定义路由规则 */}
<Routes>
{/* 一级路由 */}
<Route path='/index' element={<Index />} >
{/* 定义二级路由 */}
<Route path='/index/home' element={<Home />} />
<Route path='/index/mine' element={<Mine />} />
{/* 重定向*/}
<Route path='/index' element={<Navigate to='/index/home' />} />
{/* 404 */}
<Route path='*' element={<div>404</div>} />
</Route>
<Route path='login' element={<Login />} />
{/* 重定向 由于Routes组件中之恶能写Route或者Fragment组件,所以重定向要这么写 */}
<Route path='/' element={<Navigate to='/index' />} />
{/* 404 */}
<Route path='*' element={<div>404</div>} />
</Routes>
</div>
);
}
export default App;
3.3 定义配置式路由(跟Vue形式的路由相似)
在项目目录下新建一个router文件夹,去定义路由规则并导出。然后再组件里面去生成路由规则。
需要调用路由库提供的 useRouters 钩子函数,生成具体的路由规则。
// index.js
// 定义react路由规则
import {Navigate} from 'react-router-dom'
// 一级路由组件
import Index from '../components/Index'
import Login from '../components/Login'
// 二级路由组件
import Home from '../components/Index/Home'
import Mine from '../components/Index/Mine'
const routers = [
{
path:'/index',
element:<Index/>,
children:[
{
path:'/index/home',
element:<Home/>
},
{
path:'/index/mine',
element:<Mine/>
},
{
path:'/index',
element:<Navigate to='/index/home'/>
},
{
path:'*',
element:<div>404</div>
}
]
},
{
path:'/login',
element:<Login/>
},
{
path:'/',
element:<Navigate to='/index'/>
},
{
path:'*',
element:<div>404</div>
}
]
// 导出路由规则,然后再组件里面去生成路由规则
export default routers;
//app.jsx
import './App.css';
import React from 'react';
// 如果路由规则通过外部js文件定义,需要导入到App组件中,统一生成具体的路由规则
// 需要调用路由库提供的 useRouters 钩子函数,生成具体的路由规则。生成的路由规则是一个数组,数组中的每一项是一个对象,对象中包含path和element属性
import { useRoutes } from 'react-router-dom'
// 导入外部的路由规则
import routes from './router/index'
function App(props) {
return (
<div className='app'>
{/* 生成路由规则 */}
{
useRoutes(routes)
}
</div>
);
}
export default App;
3.4 跳路由
跳路由方式依旧是两种:
- 通过路由库提供的Link和NavLink组件去跳路由。
- 通过路由库提供的useNavigator钩子函数去创建一个路由对象去跳路由。
因为函数式组件没有this,他的props里面也没有 history,location,match这三个对象,多以不能跟类组件中那样去跳。
v6路由中组件的porps默认为空,不再注入history,location,match等属性。 v6路由提供了useNavigatot钩子函数用来跳路由(需要在路由库中来导入)。
import React, { useState } from 'react';
import { Outlet, useNavigate, useLocation, useMatch } from 'react-router-dom';
import { TabBar } from 'antd-mobile'
import { AppOutline, UserOutline } from 'antd-mobile-icons'
// 跳路由方式依旧是两种
function index(props) {
const navigator = useNavigate()
const [activeKey, setActiveKey] = useState('/index/home')
const tabs = [
{
key: '/index/home',
title: '首页',
icon: <AppOutline />,
},
{
key: '/index/mine',
title: '我的',
icon: <UserOutline />,
},
]
// const [activeKey, setActiveKey] = useState('/index/home');
const handleChange = (key) => {
setActiveKey(key);
// setActiveKey(key);
// 跳路由
// v6路由中组件的porps默认为空,不在注入history,location,match等属性
// v6路由提供了Navigator方法用来跳路由
navigator(key)
}
return (
<div>
index
{/* 充当路由组件的占位出口 */}
<Outlet />
<TabBar style={{ position: 'fixed', bottom: 5, left: 0, right: 0 }} activeKey={activeKey} onChange={handleChange}>
{tabs.map(item => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
))}
</TabBar>
</div>
);
}
export default index;
3.5 路由传参及接收路由参数
-
动态路由传参: 参数会显示在地址栏,刷新页面参数不会消失。需要在定义路由地址的时候添加一个占位符。
//定义路由地址 { path:'/detail/:id', element:<Detail/> }, //跳路由 import React from 'react'; import { useNavigate } from 'react-router-dom' function Home(props) { // 创建路由对象 const navigator = useNavigate() //跳转到商品详情 const toDetail = () => { // 传递路由参数 navigator('/detail/888') } return ( <div> home组件 <button onClick={toDetail}>跳转到商品详情</button> </div> ); } export default Home;接收路由参数: 需要从react-router-dom中导出一个useParams钩子函数去接收参数。
import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom' // 在这个文件里面接收路由参数 function Detail(props) { // 接收;路由参数 let { id } = useParams() return ( <div className='detail'> 详情页 <p>接收到的路由参数:{id}</p> </div> ); } export default Detail; -
查询字符串方式: 参数显示在地址栏,刷新页面参数不会丢失。
import React from 'react'; import { useNavigate } from 'react-router-dom' function Home(props) { // 创建路由对象 const navigator = useNavigate() //跳转到商品详情 const toDetail = () => { // 传递路由参数 // 查询字符串形式传参 navigator('/detail?id=888') // 还有一种写法 navigator({ pathname: '/detail', search: '?id=888' }) } return ( <div> home组件 <button onClick={toDetail}>跳转到商品详情</button> </div> ); } export default Home;接收参数: 需要从react-router-dom中导出一个useSearchParams钩子函数去接收参数。
import React, { useEffect } from 'react'; import {useSearchParams } from 'react-router-dom' // 在这个文件里面接收路由参数 function Detail(props) { // 接收;路由参数 // 使用useSearchParams() 接收查询在字符出串路由参数 let [params, setParams] = useSearchParams() console.log(params) return ( <div className='detail'> 详情页 {/* <p>接收到的路由参数:{id}</p> */} <p>接收到的路由参数:{params.get('id')}</p> </div> ); } export default Detail; -
state传参: 参数不会携带在地址栏,刷新参数不会丢失。
import React from 'react';
import { useNavigate } from 'react-router-dom'
function Home(props) {
// 创建路由对象
const navigator = useNavigate()
//跳转到商品详情
const toDetail = () => {
// 传递路由参数
//state传参
navigator('/detail', { state: { id: 888 } })
}
return (
<div>
home组件
<button onClick={toDetail}>跳转到商品详情</button>
</div>
);
}
export default Home;
接收参数: 需要从react-router-dom中导出一个useLocation钩子函数去接收参数。
import React, { useEffect } from 'react';
import { useLocation} from 'react-router-dom'
// 在这个文件里面接收路由参数
function Detail(props) {
// 接收;路由参数
//使用useLocation接收state传递过来的参数
let location = useLocation()
console.log(location)
return (
<div className='detail'>
详情页
<p>接收到的路由参数:{location.state.id}</p>
</div>
);
}
export default Detail;
3.6 路由懒加载
react项目在打包完成后发现 chunk.js 文件比较大,导致首屏加载需要的时间比较长,可以使用路由懒加载的方式来进行优化。
在react中要实现路由懒加载,需要从react中导入 lazy函数和Suspense组件,去完成路由懒加载。实现路由懒加载之后,只有当组件需要被渲染时才会实时去加载组件,另外,当组件被加载过以后,下一次会直接加载。
//使用Suspense组件包裹路由规则
import './App.css';
import React, { Suspense } from 'react';
// 如果路由规则通过外部js文件定义,需要导入到App组件中,统一生成具体的路由规则
// 需要调用路由库提供的 useRouters 钩子函数,生成具体的路由规则。生成的路由规则是一个数组,数组中的每一项是一个对象,对象中包含path和element属性
import { useRoutes } from 'react-router-dom'
// 导入外部的路由规则
import routes from './router/index'
function App(props) {
// console.log(useRoutes(routes))
return (
<div className='app'>
{/* 生成路由规则 */}
{/* 通过Suspense包裹路由规则 */}
<Suspense fallback={<div>...</div>}>
{
useRoutes(routes)
}
</Suspense>
</div>
);
}
export default App;
//使用lazy函数懒加载导入组件
// 定义react路由规则
import {lazy,Suspense} from 'react'
import {Navigate} from 'react-router-dom'
// 一级路由组件
// import Index2 from '../components/Index2'
// import Login from '../components/Login'
// import Detail from '../components/Detail'
// 二级路由组件
// import Home from '../components/Index/Home'
// import Mine from '../components/Index/Mine'
// 懒加载
const Index2 = lazy(()=>import('../components/Index2'))
const Login = lazy(()=>import('../components/Login'))
const Detail = lazy(()=>import('../components/Detail'))
const Home = lazy(()=>import('../components/Index/Home'))
const Mine = lazy(()=>import('../components/Index/Mine'))
const routers = [
{
path:'/index',
element:<Index2/>,
children:[
{
path:'/index/home',
element:<Home/>
},
{
path:'/index/mine',
element:<Mine/>
},
{
path:'/index',
element:<Navigate to='/index/home'/>
},
{
path:'*',
element:<div>404</div>
}
]
},
{
path:'/login',
element:<Login/>
},
{
// path:'/detail/:id',
path:'/detail',
element:<Detail/>
},
{
path:'/',
element:<Navigate to='/index'/>
},
{
path:'*',
element:<div>404</div>
}
]
// 导出路由规则,然后再组件里面去生成路由规则
export default routers;
3.7 解决路由懒加载导致页面抖动问题
由于是通过Suspense包裹所有的路由规则和lazy来实现的懒加载,所以当加载路由的时候,所有组件都会重新渲染。解决方法就是给每一个二级路由组件和三级路由组件去包裹Suspense组件。
import {Suspense} from 'react'
//封装一个函数式组件(高阶组件),来处理,也可以去加一些别的逻辑
function AuthComponent({children}){
return (
<Suspense>
{Children}
</Suspense>
)
}
//使用封装的组件包裹路由渲染的组件
const routers = [
{
path:'/index',
element : <AuthComponent> <Home/> </AuthComponent> //会将Home放在props的children里面
}
]