React Router
是一个用于 React
应用的声明式路由库。它允许开发者通过组件化的方式定义应用的路由结构,使得路由管理更加直观和可维护
安装
pnpm i react-router-dom
定义路由有两种方式,分别是对象路由和路由组件,下面将逐步介绍这两种方式的用法
对象路由通过 createBrowserRouter
数组中的每一项来定义,该方式适合复杂的情况下使用
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
function Home() {
return (
<>
<h1>Home</h1>
</>
)
}
function Cate() {
return (
<>
<h1>Cate</h1>
</>
)
}
// 核心代码
const router = createBrowserRouter([
{
// 路由访问路径
path: "/",
// 对应的页面组件
element: <Home />
},
{
path: "/cate",
element: <Cate />
}
])
const root = ReactDOM.createRoot(document.getElementById('root')!);
// 使用路由
root.render(
<RouterProvider router={router}></RouterProvider>
);
直接通过组件来定义路由,这种方式适合简单的情况下使用
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Route, Routes } from 'react-router-dom'
function Home() {
return (
<>
<h1>Home</h1>
</>
)
}
function Cate() {
return (
<>
<h1>Cate</h1>
</>
)
}
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/cate" element={<Cate />} />
</Routes>
</BrowserRouter>
);
当多个页面需要使用相同组件时,比如网站导航栏和底部,这两个组件通常会出现在所有页面当中,而这些公共组件我们没必要在多个页面单独引入,所以就需要用到嵌套路由
对象路由
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom'
// 布局路由
function Layout() {
return (
<>
<div>这是一个通用导航栏组件</div>
{/* 核心:这里一定要加 Outlet 否则无法显示子路由 */}
<Outlet />
<div>这是一个通用底部组件</div>
</>
)
}
function Home() {
return (
<>
<h1>Home</h1>
</>
)
}
function Cate() {
return (
<>
<h1>Cate</h1>
</>
)
}
function Login() {
return (
<>
<h1>Login</h1>
</>
)
}
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
children: [
{
// 定义为默认路由:当访问 / 时,会显示 Home 组件
index: true,
element: <Home />
},
{
path: "cate",
element: <Cate />
}
]
},
{
path: "/login",
element: <Login />
}
])
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
<RouterProvider router={router}></RouterProvider>
);
组件路由
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="cate" element={<Cate />} />
</Route>
{/* <Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="cate" element={<Cate />} />
</Route> */}
<Route path="/login" element={<Login />} />
</Routes>
</BrowserRouter>
当访问一个不存在的页面时会匹配到我们自定义的 404 路由
// 404页
function NotFound() {
return (
<>
<h1>404:页面未找到</h1>
</>
)
}
const router = createBrowserRouter([
...,
{
path: "*",
element: <NotFound />
}
])
<BrowserRouter>
<Routes>
...
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
通常应用于通过 id
获取接口中对应的数据
const router = createBrowserRouter([
// 单个:/cate/100
{
path: "/cate/:id",
element: <Cate />
},
// 多个:/cate/100/200
{
path: "/cate/:one/:two",
element: <Cate />
},
// 可选:/cate 或 /cate/100
{
path: "/cate/:id?",
element: <Cate />
},
// 无限:/cate/100/200/300/400/500
{
path: "/cate/*",
element: <Cate />
}
])
<BrowserRouter>
<Router>
<Routes>
{/* 路由组件不支持可选方式,但可以这样实现同样的效果 */}
<Route path="/cate" element={<Cate />} />
<Route path="/cate/:id" element={<Cate />} />
<Route path="/cate/:one/:two" element={<Cate />} />
<Route path="/cate/*" element={<Cate />} />
</Routes>
</Router>
</BrowserRouter>
获取动态路由数据
通过 useParams()
来获取动态路由的参数数据
function Cate() {
const params = useParams();
return (
<>
<h1>Cate</h1>
<pre>{JSON.stringify(params)}</pre>
</>
);
}
访问:/cate/100/200
结果:{"one":"100","two":"200"}
通过 Link
进行路由跳转,其实它的本质还是通过 a
标签进行跳转的
<Link to="/cate">跳转</Link>
{/* 最终会编译成 */}
<a href="/cate">跳转</a>
function Layout() {
return (
<>
<div>这是一个通用导航栏组件</div>
<Outlet />
<div>这是一个通用底部组件</div>
</>
)
}
function Home() {
return (
<>
<h1>Home</h1>
<Link to="/cate">跳转到分类页</Link>
<Link to="/cate/100">跳转到分类页(带参数)</Link>
</>
)
}
function Cate() {
return (
<>
<h1>Cate</h1>
<Link to="/">跳转到首页</Link>
</>
);
}
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
children: [
{
index: true,
element: <Home />
},
{
path: "cate/:id?",
element: <Cate />
}
]
}
])
更多关于 useNavigate
的用法请见下方 API
目录
import { ..., useNavigate } from 'react-router-dom'
function Home() {
const navigator = useNavigate()
return (
<>
<h1>Home</h1>
<div onClick={() => navigator("/cate")}>跳转到分类页</div>
<button onClick={() => navigator("cate/100")}>跳转到分类页(带参数)</button>
</>
)
}
function Home() {
const navigator = useNavigate()
return (
<>
<h1>Home</h1>
<Link to="/cate?title=大前端">跳转到分类页</Link>
<button onClick={() => navigator("/cate/100?title=Java")}>跳转到分类页(带参数)</button>
</>
)
}
function Cate() {
// 获取路由动态参数的数据
const params = useParams()
// 获取查询参数?后面的数据
const [searchParams] = useSearchParams()
console.log(searchParams); // URLSearchParams {size: 1}
return (
<>
<h1>Cate</h1>
<div>{JSON.stringify(params)}</div>
{/* {id: '100'} */}
<div>{JSON.stringify(searchParams.get("title"))}</div>
{/* Java */}
</>
);
}
各个主流框架的常用路由模式有两种,分别是 history
和 hash
模式, ReactRouter
分别由 createBrowerRouter
和 createHashRouter
函数负责创建
路由模式 | url表现 | 底层原理 |
---|---|---|
history | url/login | history 对象 + pushState 事件 |
hash | url/#/login | 监听 hashChange 事件 |
默认情况下像以下代码在页面被访问时尽管条件为 false
, 组件也一样会被加载。而如果使用了路由懒加载,那么只有条件满足时候 或者说 组件被使用时才会被加载,这样有助于性能优化及加载速度
{false && <Article>}
假设 pages
中有 Home
和 Cate
组件,我们可以通过 lazy
将他们定义为懒加载模式
const router = createBrowserRouter([
{
path: "/",
lazy: () => import("@/pages/Home/index.tsx")
},
{
path: "/cate",
// 可以省略:index.tsx
lazy: () => import("@/pages/Cate")
}
])
通过 loader
函数我们可以在页面 渲染之前 传递给页面指定的数据,并通过 useLoaderData
来接收数据
function Cate() {
const loaderData = useLoaderData()
console.log(loaderData); // Hello World
return ...
}
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
children: [
{
index: true,
element: <Home />
},
{
page: "cate/:id",
element: <Cate />,
loader: ({params}) => {
return "Hello World!"
}
}
]
}
])
数据也可以是异步的,通常用于获取后端接口的数据
loader: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World!")
}, 3000)
})
}
还可以获取路由参数,通过参数来获取对应的数据
{
path: "/cate/:id",
element: <Cate />,
loader: ({ params }) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`分类的参数:${params.id}`)
}, 3000)
})
}
}
该方法返回当前 location
对象,主要用于监听路由的变化执行对应的逻辑
function Home() {
let location = useLocation();
useEffect(() => {
// 逻辑代码
}, [location]);
return ...;
}
假如访问:/cate/100?title=Java#123
,那么 location
的结果如下
{"pathname":"/cate/100","search":"?title=Java","hash":"#123","state":{"data":"Hello World!"},"key":"8lu1q217"}
该方法主要用于编程式导航的跳转
function Home() {
const navigator = useNavigate()
return (
<>
<div onClick={() => navigator("/cate")}>跳转</div>
</>
)
}
replace
指定 replace: true
将替换历史记录堆栈中的当前条目,而不是添加新的条目。简单来说就是默认情况下它的值为 false
也就意味着当你从 A
页面跳转到 B
页面时,点击浏览器的回退功能可以正常回退到 A
页面。但如果将值设置为 true
则跳转到 B
页面时无法回退到 A
了,因为 A
的历史记录栈被 B
替换了
navigator("/cate", { replace: true })
<Navigate to="/cate" replace />
preventScrollReset
默认情况下该参数的值为 false
,表示组件重新渲染或重新跳转进入时会自动滚到顶部,如果设置为 true
则表示保持当前位置
state
在跳转时也可以通过 state
进行传递数据,通过 useLocation
接收数据
navigator("/cate", { state: { data: "Hello World!" } })
<Navigate to="/cate" state={{ data: "Hello World!" }} />
示例:
function Home() {
const navigator = useNavigate()
return (
<>
<h1>Home</h1>
<div onClick={() => navigator("/cate", { state: { data: "Hello World!" } })}>跳转</div>
</>
)
}
function Cate() {
const location = useLocation()
return (
<>
<h1>Cate</h1>
<div>{JSON.stringify(location.state)}</div>
{/* {"data":"Hello World!"} */}
</>
);
}
<Navigate>
该组件会在渲染时自动重定向到指定的路径。它是 useNavigate
的组件包装器,并接受与 props
相同的所有参数
我们可以应用于以下场景:当有 token
时显示页面,反之跳转到 login
页
import { Navigate } from 'react-router-dom'
const AuthRoute = ({ children }: { children: React.ReactNode }) => {
const token = "..."
if (token) {
return <>{children}</>
} else {
return <Navigate to="/login" replace />
}
}
export default AuthRoute