React-Router
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 路由
// 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.tsxlazy: () => import("@/pages/Cate")}
])
loader
通过 loader
函数我们可以在页面 渲染之前 传递给页面指定的数据,并通过 useLoaderData
来接收数据
function Cate() {const loaderData = useLoaderData()console.log(loaderData); // Hello Worldreturn ...
}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)})}
}
API
useLocation
该方法返回当前 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"}
useNavigate
该方法主要用于编程式导航的跳转
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