React-Router V6全方位讲解
前言
React-Router v6 发布于2021年底,相较于v5有很大的更新和升级,变化主要体现在下面几点:
- 不再依赖React,可以用于任何UI库中。
- 使用新声明式语法,不再使用静态的组件。
- 移除了一些API如withRouter,改为使用新Hooks。
- 支持自动生成路径。
- 更好的动态路由匹配。
- 支持嵌套路由。
- 更好的代码拆分和懒加载。
- 移除了一些不常用的API。
- 性能和包体积都有提升。
- 增加了一些对TypeScript的支持。
总体来说,React Router v6是一个遍地重构的大版本升级,使用全新的声明式API改进了路由的开发体验,并且提升了性能,虽然v6版本可以不依赖React,但是普遍还是在React中使用,下面我们也是在React项目中介绍它的基本使用方式和核心API使用场景。
路由的History模式和Hash模式
前端路由基本分成两种模式,History模式和Hash模式,这两种模式有各自的特点和使用场景
History模式
特点
- 使用HTML5 History API(pushState、replaceState和popstate事件)来管理浏览器历史记录和导航。
- 路由信息显示在URL的路径部分(例如:/about)。
- 不使用哈希符号(#),URL看起来更加干净美观。
- 可以通过服务器配置来支持直接在不同路径下加载应用,因为服务器会正确地返回主页面并在前端处理路由。
- 适合现代浏览器,支持HTML5 History API,并且需要漂亮的URL。
适用场景
当你有一个现代浏览器环境,并且你希望URL看起来更加干净,并且能够通过服务器配置来处理不同路径下的应用加载。
Hash模式
特点
- 使用URL的哈希部分(#)来模拟路由状态,即称为哈希路由。
- 路由信息保存在URL的哈希部分(例如:/#/about)。
- 适用于不支持HTML5 History API的老旧浏览器,因为哈希的变化不会发送到服务器,无需特殊服务器配置。
- 可能在URL中带有哈希,看起来可能不太美观。
适用场景
当你需要兼容老旧浏览器,或者不方便在服务器上进行配置时,可以选择哈希模式。也常用于单页应用程序(SPA)的静态文件服务器。
总结
- 使用"history"模式时,URL看起来干净,适合现代浏览器和有服务器配置的情况。
- 使用"hash"模式时,适合老旧浏览器,不需要特殊服务器配置,但URL可能会看起来较为杂乱。
- 选择合适的模式取决于你的项目需求、浏览器兼容性和服务器配置等因素。
React-Router在React项目中的使用
我们下面使用History路由模式讲解在React项目中如何使用
安装依赖包
React-Router在React项目中使用需要安装两个依赖包,分别是react-router-dom和history
,安装时需要指定版本
npm install react-router-dom@6 history@5
项目入口文件链接路由
由于我们使用的是history模式,这里我们需要导入BrowserRouter
,并在render根组件时将根组件包裹
import React from 'react'
import ReactDOM from 'react-dom/client'
import RouterApp from '@app/routerApp'
import { BrowserRouter } from 'react-router-dom'
const rootElement = document.getElementById('root') as HTMLElement
const root = ReactDOM.createRoot(rootElement)
root.render(
<BrowserRouter>
<RouterApp />
</BrowserRouter>
)
routerApp.tsx引入路由配置组件
我们这里将路由配置抽离成独立的组件,并在routerApp.tsx根组件中引入路由配置组件
import React from 'react'
import Router from '@src/router'
function RouterApp() {
return <Router />
}
export default RouterApp
使用Routes和Route组件配置路由
在v6版本中,配置路由有两种方式,使用路由组件和使用useRoutes
,这里先介绍使用路由组件的形式
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
function Router() {
return (
<Routes>
<Route path="/" element={<Login />} />
<Route path="forgetPassword" element={<ForgetPassword />} />
<Route path="main" element={<Main />} />
<Route path="*" element={<div> This Page Not Exist</div>} />
</Routes>
)
}
export default Router
在配置路由时,所有路由Route
都需要写在Routes
内部。Route
中传入path
即为路由的路径,例如上述path="forgetPassword"
即当路由为http://localhost:8000/forgetPassword
时会匹配ForgetPassword
组件。当path为/
时为默认匹配,即直接访问http://localhost:8000
时会进入登录页面。当path为*
时为通配符,这个通配符路由会匹配所有未匹配到其他路由的路径,也就是说,它是一个“捕获一切”的路由。例如我们访问http://localhost:8000/abc
此时没有匹配任何路由,页面就会显示This Page Not Exist
Route
中传入element
即为路由所匹配的组件,意味着当我们访问http://localhost:8000/forgetPassword
时,将会加载ForgetPassword
组件
使用useRoutes配置路由
import React from 'react'
import { useRoutes } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
function Router() {
const Routes = useRoutes([
{ path: '/', element: <Login /> },
{ path: 'forgetPassword', element: <ForgetPassword /> },
{ path: 'main', element: <Main /> },
{ path: '*', element: <div> This Page Not Exist</div> },
])
return Routes
}
export default Router
使用useRoutes
创建路由配置时,useRoutes
接收一个数组,数组的每一项是一个路由对象,其中的path
和element
与组件路由作用相同。useRoutes
返回一个react.element
元素
创建路由跳转链接
我们项目中默认匹配组件时Login
组件,我们可以在login组件中添加一些链接,用于跳转到其他路由
import React, { memo } from 'react'
import { Link } from 'react-router-dom'
function Login() {
return (
<div>
Login Page
<br />
<Link to="forgetPassword">forgetPassword</Link>
<br />
<Link to="main">login in</Link>
</div>
)
}
export default memo(Login)
路由跳转链接使用组件Link
,组件的to
属性的值就是会跳转到的路由路径。注意:link的to属性值,会自动拼接当前的路径,并以/分割。例如当前路径为http://localhost:8000,点击forgetPassword时会将跳转到http://localhost:8000/forgetPassword
login页面如下所示当点击
forgetPassword
、login in
时会对应跳转到ForgetPassword
和Main
页面
配置嵌套路由
我们上面配置的路由都是一级路由,日常项目中我们常会遇到多级路由,此时我们就需要使用到路由嵌套,路由嵌套同样有两种写法。例如我们上述案例登录后,在Main组件中配置两个子路由userList
和accountCenter
Main组件中配置链接
import React, { memo } from 'react'
import { Link, Outlet } from 'react-router-dom'
import './index.less'
function Main() {
return (
<div className="main">
<div className="sidebar">
<Link to="userList">userList</Link>
<br />
<Link to="accountCenter">accountCenter</Link>
</div>
<div className="content">
<Outlet />
</div>
</div>
)
}
export default memo(Main)
这里配置路由链接与一级路由类似,只是需要注意的是,这里点击userList
时,跳转的地址是拼接了父路由地址的,即会跳转到地址http://localhost:8000/main/userList
这里最关键的是Outlet
组件,该组件类似一个占位符
,即当匹配到的子路由后,子路由的内容将显示在Outlet
区域
使用Routes和Route组件
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'
function Router() {
return (
<Routes>
<Route path="/" element={<Login />} />
<Route path="forgetPassword" element={<ForgetPassword />} />
<Route path="main" element={<Main />}>
<Route index element={<UserList />} />
<Route path="userList" element={<UserList />} />
<Route path="accountCenter" element={<AccountCenter />} />
</Route>
<Route path="*" element={<div> This Page Not Exist</div>} />
</Routes>
)
}
export default Router
嵌套路由写法
在使用Routes和Route组件配置嵌套路由时,直接使用父路由包裹子路由即可,子路由匹配路径需要拼接父路由的路径,例如访问http://localhost:8000/main/userList
将在Main组件的Outlet
处显示userList组件内容索引路由解释
在上面Main的子路由中,第一个路由没有具体的path值,而是直接传入一个index
,此时该路由称为Main的索引路由。索引路由的作用是,当我们访问http://localhost:8000/main
时,并没有指定二级路由的时候,Outlet
处显示的就是该索引路由对应的内容。如果没有配置索引路由,那当我们访问http://localhost:8000/main
时,Outlet
处显示的是空白,没有任何内容。
使用useRoutes
import React from 'react'
import { useRoutes } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'
function Router() {
const Routes = useRoutes([
{ path: '/', element: <Login /> },
{ path: 'forgetPassword', element: <ForgetPassword /> },
{
path: 'main',
element: <Main />,
children: [
{ index: true, element: <UserList /> },
{ path: 'userList', element: <UserList /> },
{ path: 'accountCenter', element: <AccountCenter /> },
]
},
{ path: '*', element: <div> This Page Not Exist</div> },
])
return Routes
}
export default Router
在使用useRoutes
时,嵌套路由配置在父路由对象的children
属性中,children
属性值也是一个数组,数组的每一项都是一个子路由,其中不设置path,而是设置index: true
的,就是对应的索引路由
,其作用和上面介绍的一样。
使用动态链接NavLink
动态链接NavLink
和普通链接Link
的主要区别就是NavLink
可以获取到当前链接的路由是否是活动状态,可以根据该状态调整链接的样式。NavLink
可以根据状态传入不同的className,style,和children,在传入这些值时,需要传入一个函数,该函数接收两个参数,分别是isActive, isPending
,其中isActive
是活动状态,指当前路由是匹配状态。isPending
是进行中的状态,指路由被匹配,还在加载中,一般是懒加载的路由在加载的过程中。根据不同状态设置className
<NavLink
to="userList"
className={({ isActive, isPending }) => {
return isPending ? 'pending' : isActive ? 'active' : ''
}}
>
userList
</NavLink>
根据不同状态设置style
<NavLink
to="accountCenter"
style={({ isActive, isPending }) => {
return isPending ? { color: 'red' } : isActive ? { color: 'green' } : {}
}}
>
accountCenter
</NavLink>
根据不同状态设置children
<NavLink
to="accountCenter"
style={({ isActive, isPending }) => {
return isPending ? { color: 'red' } : isActive ? { color: 'green' } : {}
}}
>
{({ isActive, isPending }) => { return isPending ? 'pending' : isActive ? 'accountCenter' : '' }}
</NavLink>
使用参数路由,并使用useParams获取参数
参数路由路由配置
所谓参数路由,就是不使用具体的路径名,而是使用参数占位,使用Route配置参数路由写法为<Route path=":userId" element={<UserInfo />} />
,path的值是个: + 参数名
的形式,例如我们在上面例子,在userlist路由下再加入userInfo的参数子路由,参数为userId
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'
import UserInfo from '@pages/routerDemo/userInfo'
function Router() {
return (
<Routes>
<Route path="/" element={<Login />} />
<Route path="forgetPassword" element={<ForgetPassword />} />
<Route path="main" element={<Main />}>
<Route index element={<UserList />} />
<Route path="userList" element={<UserList />}>
<Route path=":userId" element={<UserInfo />} />
</Route>
<Route path="accountCenter" element={<AccountCenter />} />
</Route>
<Route path="*" element={<div> This Page Not Exist</div>} />
</Routes>
)
}
export default Router
此时我们不管访问http://localhost:8000/main/userList
下面的任何路由,最后一级都会被解析为路由参数,例如http://localhost:8000/main/userList/1001
此时最后一级路由1001
就是路由参数。此时我们就可以使用useParams
获取该参数。配置参数路由在useRoutes
中的写法如下
import React from 'react'
import { useRoutes } from 'react-router-dom'
import Login from '@pages/routerDemo/login'
import ForgetPassword from '@pages/routerDemo/forgetPassword'
import Main from '@pages/routerDemo/main'
import UserList from '@pages/routerDemo/userList'
import AccountCenter from '@pages/routerDemo/accountCenter'
import UserInfo from '@pages/routerDemo/userInfo'
function Router() {
const Routes = useRoutes([
{ path: '/', element: <Login /> },
{ path: 'forgetPassword', element: <ForgetPassword /> },
{
path: 'main',
element: <Main />,
children: [
{ index: true, element: <UserList /> },
{
path: 'userList',
element: <UserList />,
children: [
{ path: ':userId', element: <UserInfo /> }
]
},
{ path: 'accountCenter', element: <AccountCenter /> },
]
},
{ path: '*', element: <div> This Page Not Exist</div> },
])
return Routes
}
export default Router
参数路由跳转链接配置
import React, { memo } from 'react'
import { Link, Outlet } from 'react-router-dom'
function UserList() {
const userList = [
{
id: 1001,
name: '张三',
age: 18
},
{
id: 1002,
name: '李四',
age: 19
},
{
id: 1003,
name: '王五',
age: 20
}
]
return (
<div>
UserList Page
<br />
<ul>
{
userList.map((user) => (
<React.Fragment key={user.id}>
<Link to={`${user.id}`}>{user.name}</Link>
<br />
</React.Fragment>
))
}
</ul>
<br />
<Outlet />
</div>
)
}
export default memo(UserList)
使用useParams获取url参数
我们可以在链接跳转到对应参数路由后,在任意已经渲染的组件中获取到该路由参数,我们此时在UserInfo
组件中获取为例,获取后在页面中显示出来
import React, { memo } from 'react'
import { useParams } from 'react-router-dom'
function UserInfo() {
const { userId } = useParams()
return (
<div>
UserList Page {userId}
</div>
)
}
export default memo(UserInfo)
路由优先匹配具名路由
这里有个需要特别注意的点,就是当我们子路由中有参数路由
和具名路由
时,会优先匹配具名路由
,例如我在上面案例中,在userList路由下新增一个test路由
<Route path="userList" element={<UserList />}>
<Route path=":userId" element={<UserInfo />} />
<Route path="test" element={<div> This is Test Page </div>} />
</Route>
此时如果我们访问http://localhost:8000/main/userList/test
,此时匹配的就是第二个子路由,页面显示的也是This is Test Page
,此时在任何组件中使用useParams
是无法获取到参数的,这是路由匹配优先级决定的。若我们没有<Route path="test" element={<div> This is Test Page </div>} />
这个子路由,访问http://localhost:8000/main/userList/test
时最后的test就会解析成路由参数,也就可以使用useParams
获取到userId
的值,值为test
。
使用useSearchParams设置、获取搜索参数
搜索参数和url参数不同,搜索参数不会改变当前的路由路径,而是直接在路由后面添加参数,与get请求传参
写法一致,都是在路由路径的末尾加上?key=value
的形式,多个参数之间使用&
符号连接。在react-router中,我们使用useSearchParams
返回两个值,[searchParams, setSearchParams]
其中searchParams.get('参数名')
用来获取参数值,setSearchParams({key1: value1, key2: value2})
用来设置参数值,当我们调用setSearchParams
之后,当前路由上就会拼接我们对象中的所有参数。例如我们在UserInfo组件中使用
import React, { memo } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
function UserInfo() {
const { userId } = useParams()
const [searchParams, setSearchParams] = useSearchParams()
return (
<div>
UserList Page {userId} {searchParams.get('name')}
<br />
<button onClick={() => setSearchParams({ name: '张三', age: '18' })}>张三</button>
</div>
)
}
export default memo(UserInfo)
路由传参,使用useLocation获取路由信息
在我们使用Link跳转路由时,除了使用上面说的url传参和搜索参数传参
之外,还可以直接传入state参数,我们先来看一下Link
组件源码中定义的props有哪些内容我们能看到Link可以接收一个
state
的props,该props类型任意,代码中我们实现如下例如我们在UserList组件中每个跳转连接中,都将用户信息对象通过state
传入
import React, { memo } from 'react'
import { Link, Outlet } from 'react-router-dom'
function UserList() {
const userList = [
{
id: 1001,
name: '张三',
age: 18
},
{
id: 1002,
name: '李四',
age: 19
},
{
id: 1003,
name: '王五',
age: 20
}
]
return (
<div>
UserList Page
<br />
<ul>
{
userList.map((user) => (
<React.Fragment key={user.id}>
<Link to={`${user.id}`} state={{ ...user }}>{user.name}</Link>
<br />
</React.Fragment>
))
}
<Link to="test">Test</Link>
</ul>
<br />
<Outlet />
</div>
)
}
export default memo(UserList)
此时我们便可以在UserInfo
组件中使用useLocation
获取当前的路由信息,当前的路由信息中就包含state
import React, { memo, useEffect } from 'react'
import { useParams, useSearchParams, useLocation } from 'react-router-dom'
function UserInfo() {
const { userId } = useParams()
const [searchParams, setSearchParams] = useSearchParams()
const location = useLocation()
useEffect(() => {
console.log('location', location)
}, [location])
return (
<div>
UserList Page {userId} {searchParams.get('name')}
<br />
<button onClick={() => setSearchParams({ name: '张三', age: '18' })}>张三</button>
</div>
)
}
export default memo(UserInfo)
我们打印出的location
值如下此时我们使用
location.state
就可以获取到路由传过来的参数值。
使用useNavigate实现编程式导航
我们上面所用的导航形式都是使用Link
或者NavLink
标签实现,我们也可以在代码中实现导航,此时我们需要用到useNavigate
钩子函数,该钩子函数返回一个函数navigate
,navigate
可以接收两个参数,第一个参数是路由路径,第二个是一个对象,对象中可以传入一个state属性,该属性就是useLocation
获取路由信息中的state
,这种方式也成为编程式导航传参
例如我们将Main
中的Link替换成编程式导航
import React, { memo } from 'react'
import { Outlet, useNavigate } from 'react-router-dom'
import './index.less'
function Main() {
const navigate = useNavigate()
// 跳转到指定页面
const toPage = (path: string, state: { [key: string]: any }) => {
navigate(path, { state })
}
return (
<div className="main">
<div className="sidebar">
<button onClick={() => toPage('userList', { path: 'userList' })}>userList</button>
<br />
<button onClick={() => toPage('accountCenter', { path: 'accountCenter' })}>accountCenter</button>
</div>
<div className="content">
<Outlet />
</div>
</div>
)
}
export default memo(Main)
此时我们点击这两个按钮时,同样会跳转到对应路由地址,并且在对应的路由下使用useLocation
也能获取到state的值。
转载自:https://juejin.cn/post/7268011328940933155