# React路由
> React框架配套的路由系统插件 是 React Router。目前版本为V6。
## 1. 基本使用
1. 创建路由实例
createBroswerRouter或者createHashRouter
2. 引入RouterProvider组件
3. 定义相关路由组件
```jsx
// 定义路由的模块 router/index.js
import { createBrowserRouter } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';
const routes = [
{
path: '/',
element: ,
},
{
path: '/about',
element: ,
},
];
const router = createBrowserRouter(routes);
export default router;
```
```jsx
// 入口文件
import { RouterProvider } from 'react-router-dom';
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
```
## 2. 嵌套路由
实现分2步走:
1. 在指定父路由对象上添加children属性,值为路由对象数组;
2. 在父路由组件上通过Outlet组件来指定子路由组件的渲染位置
```jsx
// 定义嵌套子路由
const routes = [
{
path: '/',
element: ,
children: [
// 这是索引路由
{
// path: '',
index: true,
element: ,
},
{
path: 'about',
element: ,
},
],
},
];
```
```jsx
// 父组件 -- 布局组件
import Header from '../components/Header';
import { Outlet } from 'react-router-dom';
function Layout() {
return (
{/* 渲染子路由组件 */}
);
}
export default Layout;
```
## 3. 动态路由
> 当实现一个类似详情页的需求时,我们可能会需要使用到动态路由
例如,在商品列表页中点击某一商品获取详情。
1. 假设 商品列表 对应的 路由是 `/products`
2. 需要单独页面显示某一商品的详情,此时 路由为这么定义:`/products/details/{product-id}` 因此这就是动态路由。
3. 定义路由时,使用":"去定义路由中的参数,即`{path: '/products/details/:productId'}`
4. 访问时,会自动匹配。成功后,会将路由参数与值解析出来,存储在params对象中
5. 通过路由Hook `useParams` 来获取上面解析后的路由参数对象
```jsx
// 列表页代码
function Products() {
let [products] = useState(initialProducts);
return (
# |
Name |
Price |
Action |
{products.map((p, i) => (
{i + 1} |
{p.name} |
{p.price} |
详情
|
))}
);
}
export default Products;
// 详情页代码
import { useParams } from 'react-router-dom';
function ProductDetail() {
const { productId } = useParams();
// console.log(params);
return (
详情页
您正在浏览的商品ID:{productId}
);
}
export default ProductDetail;
```
## 4. 查询参数
> 在实际开发中,可以通过路由参数实现页面间数据传递;当然也可以通过查询参数。
下面代码演示,如果通过查询参数实现页面间数据传递:
```jsx
{products.map((p, i) => (
{i + 1} |
{p.name} |
{p.price} |
{/* 1 路由参数 在页面间传递数据 */}
{/* 详情 */}
{/* 2 查询参数 在页面间传递数据 */}
{/* 详情 */}
详情
|
))}
```
```jsx
let [searchParams] = useSearchParams();
```
## 5. 页面导航
### 5.1 声明式导航
1. Link组件
2. NavLink组件
NavLink在使用时,更容易实现选中效果。
```jsx
const handleLinkStyle = ({ isActive, isPending }) =>
isPending ? 'pending' : isActive ? 'actived' : 'link';
function Header() {
return (
);
}
```
### 5.2 编程式导航
通过 `useNavigate()`Hook 获取到 `navigate`函数,接着通过调用navigate函数实现页面跳转。
```jsx
const navigate = useNavigate();
// navigate(to: Path, option?)
// navigate(delta: number)
navigate(-1)
navigate(1)
navigate('/login')
navigate({pathname: '/login'})
navigate({pathname: '/login', search: '?id=1'})
navigate({pathname: '/login'}, {replace: true, state: {}})
```
## 6. 数据获取
> 在路由系统中,如何选择时机去获取数据?
>
> 我们知道在VueRouter中,已经提供了两种方式,都是用户能够接收并且体验良好的方案
>
> 1. 在组件渲染后,添加Loading效果后去获取数据
> 2. 在路由开启导航时先去获取数据,这样组件渲染时就会连同数据一起渲染出来了
### 6.1 Loading效果
1. 在路由组件中定义对应数据的状态对象
2. 在组件渲染后,获取数据并修改状态
3. 组件模板中通过条件渲染实现当数据没有回来时渲染Loading组件,而数据返回来后在做列表渲染即可。
### 6.2 Data Loading
1. 在定义路由的时候,给路由对象添加loader数据加载器(值为函数),它会在在导航过程中启动数据加载。
2. 在路由组件中,通过`useLoaderData()`Hook函数来获取上面loader返回的数据
## 7. 页面访问权限
> Vue Router中可以通过导航守卫钩子来拦截到所有的页面导航,根据当前用户权限决定是否继续导航。
>
> 但是React Router需要根据不同的路由定义方式来自行实现。
### 7.1 自定义hook
```js
/**
*! 自定义hook
** 1 hook 函数的名称 必须 以 'use'开头去命名
** 2 自定义hook中可以任意使用内置的所有hook函数
*/
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
const whiteList = ['/', '/about', '/login'];
export function useAuth() {
let location = useLocation();
let navigate = useNavigate();
let token = localStorage.getItem('token');
useEffect(() => {
if (!token && !whiteList.includes(location.pathname)) {
navigate(`/login?from=${location.pathname}`);
}
}, [location]);
}
```
### 7.2 Router Provider实现
```jsx
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
export default function Protected() {
const navigate = useNavigate();
// 页面访问鉴权
useAuth();
return (
受保护页面——需要登录后才可以访问
);
}
```
## 8. 其他
### 8.1 路由懒加载
> 为了提高React项目首次加载速度,推荐使用路由懒加载。
实现时,需要从React核心库中引入`lazy`函数和`Suspense`组件。像这样,
```jsx
import { createBrowserRouter } from 'react-router-dom';
import Home from '../pages/Home';
import { lazy, Suspense } from 'react';
const About = lazy(() => import('../pages/About'));
const router = createBrowserRouter([
{ path: '/', Component: Home },
{
path: '/about',
element: (
加载中...}>
),
},
]);
export default router;
```
在定义路由时,React Router还提供了一系列组件:
```jsx
import './App.css';
import { NavLink, Routes, Route } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import Home from './pages/Home';
// import About from './pages/About';
// import User from './pages/User';
import UserProfile from './pages/UserProfle';
import UserPost from './pages/UserPost';
const About = lazy(async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
return import('./pages/About.jsx');
});
const User = lazy(() => import('./pages/User'));
// 实现路由懒加载的高阶组件
function withSuspense(Component) {
return (
组件疯狂加载中...}>
);
}
function App() {
return (
} />
} />
import('./pages/UserPost.jsx')}
element={}
/>
);
}
export default App;
```
其中,`Route`组件就是定义路由的。当其path属性和当前地址匹配成功后,就会在其位置下渲染element绑定的React元素。
同时可以定义多个`Route`组件,这样就可以在页面中定义多个路由。并且这些`Route`组件必须作为`Routes`组件的直接子代元素。
为了将路由功能注入到项目中,需要在App组件中将整个项目代码包裹在一个`BrowserRouter`或者`HashRouter`组件中。
- BrowserRouter 会实现一个基于History 模式的路由
- HashRouter 会实现一个基于hash 模式的路由