React Router 6
一、概述
React Router 发布了三个不同的包:
react-router:路由核心库,提供许多组件、钩子。react-router-dom:包括了react-router所有内容,同时添加了用于 DOM 的组件,如<BrowserRouter>。react-router-native:包括了react-router所有内容,同时添加了用于 React Native 的 API,如<NativeRouter>。
与 React Router 5.x 版本的区别:
内置组件的变化:移除
<Switch/>,新增<Routes/>……语法变化:
component={About}变成element={<About/>}……新增 hook:
useParams、useNavigate、useMatch……官方明确表示推荐使用函数式组件。
……
二、快速入门
安装 6 版本的 React Router。
npm install react-router-domindex.js 文件引入 <BrowserRouter>。
// 从 react-dom/client 引入 ReactDOM
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
// React 18 的语法发生改变了
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
)App.js 设置路由链接和注册路由。<Route caseSensitive> 属性用于指定匹配时是否区分大小写(默认为 false)。
import { NavLink, Routes, Route } from 'react-router-dom'
import About from './components/About/About'
import Hello from './components/Hello/Hello'
// React 18 默认使用函数式组件了
export default function App() {
return (
<div>
<NavLink to="/about">About</NavLink>
<NavLink to="/hello">Hello</NavLink>
<hr />
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/hello" element={<Hello />}></Route>
</Routes>
</div>
)
}三、组件
1. 路由模式
1)<BrowserRouter>
<BrowserRouter> 用于包裹整个应用。
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
)2)<HashRouter>
作用与 <BrowserRouter> 一样,但 <HashRouter> 修改的是地址栏的 hash 值。
6.x 版本中 <HashRouter>、<BrowserRouter> 的用法与 5.x 相同。
2. <Routes/> 与 <Route/>
① 6 版本中移除了先前的 <Switch>,引入了新的替代者:<Routes>。
② <Routes> 和 <Route> 要配合使用,且必须要用 <Routes> 包裹 <Route>。
③ <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
④ <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。
⑤ 当 URL 发生变化时,<Routes> 都会查看其所有子 <Route> 元素以找到最佳匹配并呈现组件。
⑥ <Route> 也可以嵌套使用,且可配合 useRoutes() 配置“路由表”,但需要通过 <Outlet> 组件来渲染其子路由。
⑦ <Route> 的 exact:Router6.x 不再支持该属性。
<Routes>
{/* path属性用于定义路径,element属性用于定义当前路径所对应的组件 */}
<Route path="/login" element={<Login />}></Route>
{/* 用于定义嵌套路由,home是一级路由,对应的路径/home */}
<Route path="/home" element={<Home />}>
{/* test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2 */}
<Route path="/home/test1" element={<Test/>}></Route>
<Route path="test2" element={<Test2/>}></Route>
</Route>
{/* Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx */}
<Route path="/users">
<Route path="xxx" element={<Demo />} />
</Route>
{/* 404 页 */}
<Route path="*" element={<NotFound />} />
</Routes>3. 改变路由
1)<Link>
修改 URL,且不发送网络请求(路由链接)。
import { Link } from "react-router-dom";
function Test() {
return (
<div>
<Link to="/路径">按钮</Link>
</div>
);
}2)<NavLink> 路由高亮
实现导航的“高亮”效果,6 版本不能直接指定高亮类名,需要通过函数返回。该函数传入一个对象,类似于 {isActive: true} 标识路由是否被激活。
默认情况下,当 Home 的子组件匹配成功,Home 的导航也会高亮,end 属性可移除该效果。
// NavLink 默认类名是 active,下面是指定自定义类名
// 自定义样式
// 这里的 isActive 是个 boolean 值,如果你激活了对应路由就会返回 true
<NavLink
to="login"
className={({ isActive }) => {
console.log('home', isActive)
return isActive ? 'list-group-item myActive' : 'list-group-item'
}}
>about</NavLink>
// 默认情况下,当 Home 的子组件匹配成功,Home 的导航也会高亮
// 当 NavLink 上添加了 end 属性后,若 Home 的子组件匹配成功,则 Home 的导航没有高亮效果。
<NavLink to="home" end >home</NavLink>可以把这个逻辑抽离出来。
function computeClassName({isActive}){
return isActive?"list-group-item myActive":"list-group-item";
}
<NavLink className={computeClassName} to="/about">About</NavLink>
<NavLink className={computeClassName} to="/home">Home</NavLink>3)<Navigate> 重定向
只要 <Navigate> 组件被渲染,就会修改路径,切换视图。可用于路由重定向。
import { NavLink, Routes, Route, Navigate } from 'react-router-dom'
import About from './components/About/About'
import Hello from './components/Hello/Hello'
export default function App() {
return (
<div>
<NavLink to="/about">About</NavLink>
<NavLink to="/hello">Hello</NavLink>
<hr />
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/hello" element={<Hello />}></Route>
<Route path="/" element={<Navigate to="/about" />}></Route>
</Routes>
</div>
)
}replace 属性用于控制跳转模式(push 或 replace,默认是 push)。
import React, { useState } from 'react'
import { Navigate } from 'react-router-dom'
export default function Home() {
const [sum, setSum] = useState(1)
return (
<div>
<h1>Home</h1>
{/* 根据sum的值决定是否切换视图 */}
{sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true} />}
<button onClick={() => setSum(2)}>将sum变为 2</button>
</div>
)
}4. useRoutes() 路由表
根据路由表,动态创建 <Routes> 和 <Route>。所以路由规则可以单独抽出一个模块。
// 路由表
// src/routes/index.js
import { Navigate } from 'react-router-dom'
import About from '../components/About/About'
import Hello from '../components/Hello/Hello'
const routes = [
{
path: '/about',
element: <About />,
},
{
path: '/hello',
element: <Hello />,
children:[
{
path:"news",
element:<News/>
},
{
path:"message",
element:<Message/>
},
]
},
{
path: '/',
element: <Navigate to="/about" />,
},
]
export default routes引入路由表。
// 引入路由表
// src/App.js
import { NavLink, useRoutes } from 'react-router-dom'
import routes from './routes'
export default function App() {
// 生成路由规则
const element = useRoutes(routes)
return (
<div>
<NavLink to="/about">About</NavLink>
<NavLink to="/hello">Hello</NavLink>
<hr />
{element}
</div>
)
}5. <Outlet> 嵌套路由
嵌套路由中,需要使用 <Outlet> 设置子路由的路由出口,即在何处渲染子路由。
设置二级路由链接时,可以是 to="news"、to="./news",但不能是 to="/news"。
不使用路由表的嵌套路由
// App.js
export default function App() {
return (
<div>
<NavLink to="about">About</NavLink>
<NavLink to="hello">Hello</NavLink>
<hr />
<Routes>
<Route path="about" element={<About />} />
<Route path="hello" element={<Hello />}>
<Route path="news" element={<News />} />
<Route path="message" element={<Message />} />
</Route>
<Route path="/" element={<Navigate to="about" />} />
</Routes>
</div>
)
}使用路由表的嵌套路由
// 路由表
const routes = [
{
path: '/about',
element: <About />,
},
{
path: '/hello',
element: <Hello />,
// 定义二级路由,注意不要加 /
children: [
{
path: 'news',
element: <News />,
},
{
path: 'message',
element: <Message />,
},
],
},
{
path: '/',
element: <Navigate to="/about" />,
},
]在新版路由中 to="路径" 可以不写父路由路径了,但千万不要画蛇添足在前面多添加一个 /。
// Hello 子组件
import React, { Fragment } from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default function Hello() {
return (
<Fragment>
<h2>I am Hello!</h2>
{/* 子路由链接 */}
<NavLink to="news">News</NavLink>
<NavLink to="message">Message</NavLink>
<hr />
{/* 子路由出口 */}
<Outlet></Outlet>
</Fragment>
)
}路由链接中的 to 属性值,可以是
to="/home/news",即全路径(推荐这样写,不然直接看不知道是不是子路由)to="./news",即相对路径to="news"
四、Hooks
1. 路由传参
以不使用路由表为例。
1)传递 params 参数
注册路由时声明 params 参数,和 React Router 5 一样。
export default function App() {
return (
<div>
<Routes>
<Route path="hello" element={<Hello />}>
<Route path="message" element={<Message />}>
<Route path="detail/:id/:name/:age" element={<Detail />} />
</Route>
</Route>
</Routes>
</div>
)
}传递参数。
<Link to={`detail/${item.id}/${item.name}/${item.age}`}>{item.name}</Link>使用 useParams() 接收 params 参数。useParams() 返回一个参数对象。
import React from 'react'
import { useParams, useMatch } from 'react-router-dom'
export default function Detail() {
// 解构赋值
const { id, name, age } = useParams()
return (
<div>
<li>id:{id}</li>
<li>name:{name}</li>
<li>age:{age}</li>
</div>
)
}还可以使用 useMatch(路由路径)。
useMatch()是 React Router v6 中的一个 Hook,它可以用来检查当前的 URL 是否匹配给定的路径。如果匹配,它会返回一个包含了 URL 参数和其他信息的对象;如果不匹配,它会返回 null。
2)传递 search 参数
和 5 版本一样,正常注册路由即可。
<Route path="detail" element={<Detail />} />传递参数。
<Link to={`detail?id=${item.id}&name=${item.name}&age=${item.age}`}>{item.name}</Link>使用 useSearchParams() 接收参数。该方法返回一个包含两个元素的数组:search 参数和修改 search 参数的方法。
import React from 'react'
import { useSearchParams } from 'react-router-dom'
export default function Detail() {
// 数组的解构赋值
const [searchParams, setSearchParams] = useSearchParams()
// 需要调用 get() 方法获取对应的参数值
const id = searchParams.get('id')
const name = searchParams.get('name')
const age = searchParams.get('age')
function change() {
setSearchParams('id=666&name=Lily&age=888')
}
return (
<div>
<li>id:{id}</li>
<li>name:{name}</li>
<li>age:{age}</li>
<button onClick={change}>Change search params</button>
</div>
)
}还可以使用 useLocation()。useLocation 是 React Router 提供的一个 Hook,它可以用来访问当前 URL 的 location 对象。这个对象包含了当前 URL 的路径、查询参数、哈希值等信息。
3)传递 state 参数
和 5 版本一样,正常注册路由即可。
<Route path="detail" element={<Detail />} />传递参数,这里相较于 5 版本有所不同,不必写到一个对象里面。
<Link to="detail" state={{ id: item.id, name: item.name, age: item.age }}>
{item.name}
</Link>使用 useLocation() 接收参数。该方法返回路由组件的 location 对象,就是 5 版本路由组件的 location 属性,其中包含 state 参数。
import { useLocation } from 'react-router-dom'
export default function Detail() {
// 连续解构赋值
const {
state: { id, name, age },
} = useLocation()
return (
<div>
<li>id:{id}</li>
<li>name:{name}</li>
<li>age:{age}</li>
</div>
)
}2. 编程式路由导航
1)useNavigate()
useNavigate() 返回一个函数,调用该函数实现编程式路由导航。函数有两种参数传递方式。
第一种方式传递两个参数:路由和相关参数。参数只能设置 replace 和 state。想要传递 params 和 search 参数直接在路由带上。
第二种方式传递数字,代表前进或后退几步。
import React, { useState } from 'react'
import { Outlet, useNavigate } from 'react-router-dom'
export default function Message() {
const [list] = useState([
{ id: 1, name: 'Bruce', age: 33 },
{ id: 2, name: 'You', age: 3 },
{ id: 3, name: 'React', age: 333 },
])
const navigate = useNavigate()
function showDetail(item) {
{/* 路由传递search参数 */}
{/* navigate(`detail?id=${id}&title=${title}&content=${content}`,{replace:false}) */}
{/* 路由传递params参数 */}
{/* navigate(`detail/${id}/${title}/${content}`,{replace:false}) */}
navigate('detail', {
replace: true, // 默认 false
state: {
id: item.id,
name: item.name,
age: item.age,
},
})
}
function back() {
navigate(1)
}
function forward() {
navigate(-1)
}
return (
<div>
<ul>
{list.map((item) => {
return (
<li key={item.id}>
<button onClick={() => showDetail(item)}>查看详情</button>
<button onClick={back}>后退</button>
<button onClick={forward}>前进</button>
</li>
)
})}
</ul>
<Outlet></Outlet>
</div>
)
}2)withRouter 的替换者
这是 5 版本的时候。
借助 this.prosp.history 对象上的 API 对操作路由跳转、前进、后退
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go(1)可以利用 react-router-dom 对象下的 withRouter 函数来对我们导出的 Header 组件进行包装,这样我们就能获得一个拥有 history 对象的一般组件。
withRouter 可以加工一般组件(即非路由组件),让一般组件具备路由组件所持有的 API。但 v6 版本中已废除,只有 useNavigate 可以使用,但这个只支持函数式组件,怎么办?自己实现 withRouter。
import { useState } from "react"
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"
// 高阶组件: 函数
function withRouter(WrapperComponent) {
return function(props) {
// 1.导航
const navigate = useNavigate()
// 2.动态路由的参数: /detail/:id
const params = useParams()
// 3.查询字符串的参数: /user?name=why&age=18
const location = useLocation()
const [searchParams] = useSearchParams()
const query = Object.fromEntries(searchParams)
const router = { navigate, params, location, query }
return <WrapperComponent {...props} router={router}/>
}
}
export default withRouter3. Other Hooks
1)useMatch()
返回路由组件的 match 数据,即 5 版本中的 match 属性。
必须传入该组件对应的路由规则才能正确返回,否则返回 null。
// Detail.jsx
import { useParams, useMatch } from 'react-router-dom'
export default function Detail() {
const match = useMatch('hello/message/detail/:id/:name/:age')
console.log(match)
return (
<div>
<li>id</li>
</div>
)
}
/*
params: {id: '1', name: 'Bruce', age: '33'}
pathname: "/hello/message/detail/1/Bruce/33"
pathnameBase: "/hello/message/detail/1/Bruce/33"
pattern: {path: 'hello/message/detail/:id/:name/:age', caseSensitive: false, end: true}
*/2)useInRouterContext()
如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。即组件有没有被包裹在 <BrowserRouter> 这种东西里面。这个对第三方组件库有用处。
3)useNavigationType()
返回当前的导航类型(用户是如何来到当前页面的)。
返回值:POP、PUSH、REPLACE。
POP 是指在浏览器中直接打开了这个路由组件(刷新页面)。
4)useOutlet()
用来呈现当前组件中渲染的嵌套路由。
const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则返回 null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象5)useResolvedPath()
给定一个 URL 值,解析其中的:path、search、hash 值。
const res = useResolvedPath('/user?id=001&name=Bruce#React')
console.log(res)
/*
hash: '#React'
pathname: '/user'
search: '?id=001&name=Bruce'
*/