React 路由
路由介绍
TIP
现代的前端应用大多都是 SPA(单页应用程序),也就是只有一个 HTML 页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由是一套映射规则,在 React 中,是 URL 路径 与 组件 的对应关系
- 使用 React 路由简单来说,就是配置 路径和组件(配对)
- 想要实现单页应用程序(SPA),须要使用到路由 react-router
- 官网:react-router
路由基础
路由基本使用
- 安装 V6 版本路由
yarn add react-router-dom
react-router-dom
这个包提供了四个核心的组件
import { HashRouter, Routes, Route, Link } from 'react-router-dom'
- 使用
HashRouter
或BrowserRouter
包裹整个应用,一个项目中只会有一个 Router - 使用 Link 指定导航链接
- 使用
Route
指定路由规则,在哪里写的 Route,最终匹配到的组件就会渲染到这
import React, { PureComponent } from 'react'
import Home from '../components/Home.js'
import About from '../components/About.js'
import About2 from '../components/About2.js'
import About3 from '../components/About3.js'
import NoMatch from '../components/NoMatch.js'
import { HashRouter, Routes, Route, Link } from 'react-router-dom'
export default class index extends PureComponent {
render() {
return (
<HashRouter>
<Link to="/">Home</Link>
{/* 动态路由 */}
<Link to="/about/123456">About</Link>
{/* 传参 */}
<Link to="/about2" state={{ age: '16', name: 'sky' }}>
About2
</Link>
<Link
to="/about3/123?name=sky&age=16"
state={{ age: '16', name: 'sky' }}
>
About3
</Link>
{/* Routes 和 V5版本的路由中的 Switch一样 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about/:userId" element={<About />} />
<Route path="/about2" element={<About2 />} />
<Route path="/about3/:id" element={<About3 />} />
{/* 路由不存在则匹配*号路由 */}
<Route path="*" element={<NoMatch />} />
</Routes>
</HashRouter>
)
}
}
注意
- 他们都需要包裹在
BrowserRouter
或者HashRouter
里面 Route
需要包裹在Routes
里面,跟以前的Switch
一样Route
里面渲染组件方式是element = {<组件名/>}
路由参数获取
- 点击路由基本使用示例代码中的 About3
<Link to='/about3/123?name=sky&age=16' state={{age: '16', name:"sky"}}>About3</Link>
- 在 About3 中获取路由传参
import React from 'react'
import { useSearchParams, useParams, useLocation } from 'react-router-dom'
export default function About3() {
const params = useParams()
const location = useLocation()
let [searchparams] = useSearchParams()
console.log('params', params)
console.log('location', location)
console.log('searchParams', searchparams.get('name'))
console.log('searchParams', searchparams.get('age'))
return <div>About3</div>
}
- 获取参数结论
- useParams 获取 params 传参,本例中为
/123
- useLocation 获取传入 state,本例为
state: {age: '16', name: 'sky'}
- useSearchParams 获取入 query,本例为
?name=sky&age=16
- useParams 获取 params 传参,本例中为
Router 详细说明
- Router 组件:包裹整个应用,一个 React 应用只需要使用一次
- 两种常用 Router:
HashRouter
和BrowserRouter
- HashRouter:使用 URL 的哈希值实现(
localhost:3000/#/first
) - BrowserRouter:使用 H5 的 history API 实现(
localhost:3000/first
)
最佳实践
import { HashRouter as Router, Route, Link } from 'react-router-dom'
Link 与 NavLink
Link
组件最终会渲染成 a 标签,用于指定路由导航
- to 属性,将来会渲染成 a 标签的 href 属性
Link
组件无法实现导航的高亮效果
NavLink
组件,一个更特殊的Link
组件,可以用用于指定当前导航高亮
- to 属性,用于指定地址,会渲染成 a 标签的 href 属性
- className,自定义激活时的样式名 可以为字符串或者函数
- end 匹配子路由时是否高亮
- caseSensitive 代表匹配路径时是否区分大小写
Route
- path 的说明
- 默认情况下,/能够匹配任意/开始的路径
- 如果 path 的路径匹配上了,那么就可以对应的组件就会被 render
- 如果 path 没有匹配上,那么会 render null
- 如果没有指定 path,那么一定会被渲染
- component 属性变成 element
- exact 的说明, exact 表示精确匹配某个路径
- 一般来说,如果路径配置了 /, 都需要配置 exact 属性
Routes 与 404
通常,我们会把
Route
包裹在一个Routes
组件中在
Routes
组件中,不管有多少个路由规则匹配到了,都只会渲染第一个匹配的组件通过
Routes
组件非常容易的就能实现 404 错误页面的提示
/* Routes 和 V5版本的路由中的 Switch一样 */
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about/:userId" element={<About />} />
<Route path="/about2" element={<About2 />} />
<Route path="/about3/:id" element={<About3 />} />
{/* 路由不存在则匹配*号路由 */}
<Route path="*" element={<NoMatch />} />
</Routes>
嵌套路由的配置
嵌套路由一般是配合 Outlet 组件使用,此组件类似于 Vue 的 router-view 组件,告知子路由应该渲染在什么位置
- 一级路由配置
import { BrowserRouter, Routes, Route, Navigate, Link } from 'react-router-dom'
import Design from '../view/design/index' // 存在二级路由
import Code from '../view/code/index'
import NotPage from '../view/notPage'
const Nesting = () => {
return (
<div>
{/* HashRouter哈希路由 还是 Browser路由随需求选择 */}
<BrowserRouter>
{/* 一级路由 */}
<Link style={{ marginLeft: '12px' }} to="/code">
code一级路由
</Link>
<Link style={{ marginLeft: '12px' }} to="/design">
design一级路由
</Link>
<Routes>
{/* 默认初始化入口/code* /}
<Route path="/" element={<Navigate to="/code" />} />
{/* Code模块路由 */}
<Route path="code/*" element={<Code />} />
{/* Design模块路由 */}
<Route path="/design/*" element={<Design />} />
{/* 404页面 */}
<Route path="*" element={<NotPage />} />
</Routes>
</BrowserRouter>
</div>
)
}
export default Nesting
- 二级路由配置
// view/design/index.js 文件
import { Routes, Route, Link } from 'react-router-dom'
import { Component } from 'react'
export default class Design extends Component {
render() {
return (
<div>
<header>Design页面</header>
<Link to="./list">列表-二级路由</Link>
<Link style={{ marginLeft: '12px' }} to="./addedit">
新增编辑-二级路由
</Link>
<div>
<Routes>
<Route path="/list" element={<div>设计列表</div>} />
<Route path="/addedit" element={<div>设计新增编辑</div>} />
</Routes>
</div>
</div>
)
}
}
- 在 v5 版本中,路由的重定向使用的是 Redirect,在 v6 版本中使用的是 Navigate
编程式导航
- 场景:点击登录按钮,登录成功后,通过代码跳转到后台首页
- 编程式导航:通过 JS 代码来实现页面跳转
- 使用编程式导航
import React from 'react'
import { Link, useNavigate } from 'react-router-dom'
function RouterNav() {
const list = [
{ id: '001', title: '消息1' },
{ id: '002', title: '消息2' },
{ id: '003', title: '消息3' }
]
// 编程式导航
const navigate = useNavigate()
function replaceShow(id, title) {
console.log('id, title :>> ', id, title)
navigate(`/home/message/detail`, { replace: true, state: { id, title } })
}
function pushShow(id, title) {
console.log('object :>> ', id, title)
navigate(`/home/message/detail`, { replace: false, state: { id, title } })
}
return (
/* <></> 用在react中,其实算是 标签的一个语法糖 表示一个Dom片段
它可以在一个内存里面创建一个Dom节点 但是并不在Dom模板上渲染,进而提升性能 */
<>
<ul>
{list.map((i) => (
<li key={i.id}>
<Link to={`/home/message/detail/${i.id}/${i.title}`}>
{i.title}
</Link>
<button
onClick={() => {
replaceShow(i.id, i.title)
}}
>
replace
</button>
<button
onClick={() => {
pushShow(i.id, i.title)
}}
>
push
</button>
</li>
))}
</ul>
<hr />
<button onClick={() => navigate(1)}>前进</button>
<button onClick={() => navigate(-1)}>后退</button>
</>
)
}
export default RouterNav
- 渲染到页面
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import RouterNav from './programmedRouteNav/index' // 编程式导航
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<BrowserRouter>
<RouterNav />
</BrowserRouter>
</React.StrictMode>
)
useRoutes() 路由表
TIP
作用:根据路由表,动态创建<Routes>
和<Route>
import React from 'react'
import { NavLink, useRoutes, Navigate } from 'react-router-dom'
const config = [
{
path: '/',
element: <Navigate to="/login" />
},
{
path: '/login',
element: <div>login</div>
},
{
path: '/code',
element: <div>code</div>
},
{
path: '*',
element: <div>404</div>
}
]
function computedClassName({ isActive }) {
return isActive ? 'demo' : ''
}
export default function RoutesPage() {
// useRoutes可以用路由表生成<Routes>...</Routes>结构
// 根据路由表生成对应的路由规则
let element = useRoutes(config)
return (
<>
<div>
{/* 路由链接 */}
<NavLink className={computedClassName} to="/login">
login
</NavLink>
<NavLink className={computedClassName} to="/code">
code
</NavLink>
</div>
{/* 注册路由 */}
{element}
</>
)
}
V6 与 V5 版本的比较
Switch 标签替换成 Routes
原本渲染组件的方式由
<Route path="/" component={Home}>
变为<Route path="/" element={<Home/>}
(其中 Home 是对应的组件)编程式导航由
useHistory
替换成useNavigate
重定向由
Redirect
替换成Navigate
- V5 版本写法
import { Redirect } from 'react-router-dom' ;<Redirect to="/home" />
- V6 版本写法
import { Navigate } from 'react-router-dom' ;<Route path="/" element={<Navigate to="/about" />} />
新增
useRoutes
来替代版本 5 需要安装react-router-config
插件才能实现的功能NavLink 选中高亮
- 在 v5 版本中,实现 NavLink 高亮使用的是 NavLink 组件标签中的 activeClassName 属性,当你点击 NavLink 标签时,加哪个样式的类名
<NavLink activeClassName='demo' className="list-group-item" to="/about">About</NavLink> <NavLink activeClassName='demo' className="list-group-item" to="/home">Home</NavLink>
在 v6 版本中,想要实现自定义的类名,需要把 className 的值写成一个函数
import React, { PureComponent } from 'react' import Home from '../components/Home.js' import About from '../components/About.js' import { HashRouter, Routes, Route, NavLink } from 'react-router-dom' import './index.css' // 抽离激活样式 function computedClassName({ isActive }) { return isActive ? 'demo' : '' } export default class index extends PureComponent { render() { return ( <HashRouter> <NavLink to="/" className={computedClassName}> Home </NavLink> {/* 默认情况下,当 About 的子组件匹配成功,About 的导航也会高亮, 当 NavLink 上添加了 end 属性后,若 About 的子组件匹配成功,则 About 的导航没有高亮效果 */} <NavLink to="/about/123456" className={computedClassName} end> About </NavLink> {/* Routes 和 V5版本的路由中的 Switch一样 */} <Routes> <Route path="/" element={<Home />} /> <Route path="/about/:userId" element={<About />} /> </Routes> </HashRouter> ) } }
路由懒加载
import { createElement, lazy, Suspense } from 'react'
import { useRoutes, NavLink, Navigate } from 'react-router-dom'
import Layout from './index'
const Loading = () => {
return (
<div
style={{
background: 'rgba(0,0,0,0.3)',
color: '#ffffff',
height: '100vh',
width: '100vh',
overflow: 'hidden',
lineHeight: '50vh',
position: 'fixed',
top: 0,
left: 0,
fontSize: '60px',
textAlign: 'center'
}}
>
加载中...
</div>
)
}
// lazy实现路由懒加载
// Suspense 网络异常/网络状态不好的时候加载中
const lazyLoad = (src) => (
<Suspense fallback={<Loading />}>{createElement(lazy(src))}</Suspense>
)
const config = [
{
path: '/',
element: <Navigate to="/layout" /> // 重定向
},
{
path: '/layout',
element: <Layout /> // 基础布局不需要懒加载
},
{
path: '/root',
element: lazyLoad(() => import('../view/root'))
},
{
path: '/code',
element: lazyLoad(() => import('../view/code/index'))
}
]
export default function RenderLazy() {
// useRoutes可以用路由表生成<Routes>...</Routes>结构
// 根据路由表生成对应的路由规则
const element = useRoutes(config)
return (
<div>
<div>
{/* 路由链接 */}
<NavLink to="/">Layout</NavLink>
<NavLink to="/root">root</NavLink>
<NavLink to="/code">code</NavLink>
</div>
{/* 注册路由 */}
{element}
</div>
)
}