|
@@ -0,0 +1,288 @@
|
|
|
|
+# 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: <Home />,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ path: '/about',
|
|
|
|
+ element: <About />,
|
|
|
|
+ },
|
|
|
|
+];
|
|
|
|
+
|
|
|
|
+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(
|
|
|
|
+ <React.StrictMode>
|
|
|
|
+ <RouterProvider router={router} />
|
|
|
|
+ </React.StrictMode>
|
|
|
|
+);
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+## 2. 嵌套路由
|
|
|
|
+
|
|
|
|
+实现分2步走:
|
|
|
|
+
|
|
|
|
+1. 在指定父路由对象上添加children属性,值为路由对象数组;
|
|
|
|
+2. 在父路由组件上通过Outlet组件来指定子路由组件的渲染位置
|
|
|
|
+
|
|
|
|
+```jsx
|
|
|
|
+// 定义嵌套子路由
|
|
|
|
+const routes = [
|
|
|
|
+ {
|
|
|
|
+ path: '/',
|
|
|
|
+ element: <Layout />,
|
|
|
|
+ children: [
|
|
|
|
+ // 这是索引路由
|
|
|
|
+ {
|
|
|
|
+ // path: '',
|
|
|
|
+ index: true,
|
|
|
|
+ element: <Home />,
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ path: 'about',
|
|
|
|
+ element: <About />,
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ },
|
|
|
|
+];
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+```jsx
|
|
|
|
+// 父组件 -- 布局组件
|
|
|
|
+import Header from '../components/Header';
|
|
|
|
+import { Outlet } from 'react-router-dom';
|
|
|
|
+
|
|
|
|
+function Layout() {
|
|
|
|
+ return (
|
|
|
|
+ <div className="layout">
|
|
|
|
+ <header>
|
|
|
|
+ <Header />
|
|
|
|
+ </header>
|
|
|
|
+ <main style={{ padding: '20px' }}>
|
|
|
|
+ {/* 渲染子路由组件 */}
|
|
|
|
+ <Outlet />
|
|
|
|
+ </main>
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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 (
|
|
|
|
+ <div className="products">
|
|
|
|
+ <table>
|
|
|
|
+ <thead>
|
|
|
|
+ <tr>
|
|
|
|
+ <th>#</th>
|
|
|
|
+ <th>Name</th>
|
|
|
|
+ <th>Price</th>
|
|
|
|
+ <th>Action</th>
|
|
|
|
+ </tr>
|
|
|
|
+ </thead>
|
|
|
|
+ <tbody>
|
|
|
|
+ {products.map((p, i) => (
|
|
|
|
+ <tr key={p.id}>
|
|
|
|
+ <td>{i + 1}</td>
|
|
|
|
+ <td>{p.name}</td>
|
|
|
|
+ <td>{p.price}</td>
|
|
|
|
+ <td>
|
|
|
|
+ <Link to={{ pathname: `/products/details/${p.id}` }}>详情</Link>
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ ))}
|
|
|
|
+ </tbody>
|
|
|
|
+ </table>
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default Products;
|
|
|
|
+// 详情页代码
|
|
|
|
+import { useParams } from 'react-router-dom';
|
|
|
|
+
|
|
|
|
+function ProductDetail() {
|
|
|
|
+ const { productId } = useParams();
|
|
|
|
+ // console.log(params);
|
|
|
|
+ return (
|
|
|
|
+ <div className="detail">
|
|
|
|
+ <h3>详情页</h3>
|
|
|
|
+ <p>您正在浏览的商品ID:{productId}</p>
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default ProductDetail;
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+## 4. 查询参数
|
|
|
|
+
|
|
|
|
+> 在实际开发中,可以通过路由参数实现页面间数据传递;当然也可以通过查询参数。
|
|
|
|
+
|
|
|
|
+下面代码演示,如果通过查询参数实现页面间数据传递:
|
|
|
|
+
|
|
|
|
+```jsx
|
|
|
|
+<tbody>
|
|
|
|
+ {products.map((p, i) => (
|
|
|
|
+ <tr key={p.id}>
|
|
|
|
+ <td>{i + 1}</td>
|
|
|
|
+ <td>{p.name}</td>
|
|
|
|
+ <td>{p.price}</td>
|
|
|
|
+ <td>
|
|
|
|
+ {/* 1 路由参数 在页面间传递数据 */}
|
|
|
|
+ {/* <Link to={{ pathname: `/products/details/${p.id}` }}>详情</Link> */}
|
|
|
|
+ {/* 2 查询参数 在页面间传递数据 */}
|
|
|
|
+ {/* <Link to={`/products/detail?pid=${p.id}`}>详情</Link> */}
|
|
|
|
+ <Link to={{ pathname: '/products/detail', search: '?id=1' }}>
|
|
|
|
+ 详情
|
|
|
|
+ </Link>
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ ))}
|
|
|
|
+</tbody>
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+```jsx
|
|
|
|
+let [searchParams] = useSearchParams();
|
|
|
|
+<div className="detail">
|
|
|
|
+ {/* <NavLink to="..">Back</NavLink> */}
|
|
|
|
+ <a
|
|
|
|
+ href="#"
|
|
|
|
+ onClick={() => {
|
|
|
|
+ // go('/products');
|
|
|
|
+ // go({ pathname: '/products' });
|
|
|
|
+ go(-1);
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ Back
|
|
|
|
+ </a>
|
|
|
|
+ <h3>详情页</h3>
|
|
|
|
+ <p>
|
|
|
|
+ 您正在浏览的商品ID:{searchParams.get('pid')}
|
|
|
|
+ </p>
|
|
|
|
+</div>
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+## 5. 页面导航
|
|
|
|
+
|
|
|
|
+### 5.1 声明式导航
|
|
|
|
+
|
|
|
|
+1. Link组件
|
|
|
|
+2. NavLink组件
|
|
|
|
+
|
|
|
|
+NavLink在使用时,更容易实现选中效果。
|
|
|
|
+
|
|
|
|
+```jsx
|
|
|
|
+const handleLinkStyle = ({ isActive, isPending }) =>
|
|
|
|
+ isPending ? 'pending' : isActive ? 'actived' : 'link';
|
|
|
|
+
|
|
|
|
+function Header() {
|
|
|
|
+ return (
|
|
|
|
+ <div className="header">
|
|
|
|
+ <ul>
|
|
|
|
+ <li>
|
|
|
|
+ <NavLink className={handleLinkStyle} to="/">
|
|
|
|
+ 主页
|
|
|
|
+ </NavLink>
|
|
|
|
+ </li>
|
|
|
|
+ <li>
|
|
|
|
+ <NavLink className={handleLinkStyle} to={{ pathname: '/about' }}>
|
|
|
|
+ 关于
|
|
|
|
+ </NavLink>
|
|
|
|
+ </li>
|
|
|
|
+ <li>
|
|
|
|
+ <NavLink className={handleLinkStyle} to={{ pathname: '/products' }}>
|
|
|
|
+ 商品
|
|
|
|
+ </NavLink>
|
|
|
|
+ </li>
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+ );
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### 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效果
|
|
|
|
+
|
|
|
|
+### 6.2 Data Loading
|
|
|
|
+
|
|
|
|
+1. 在定义路由的时候,给路由对象添加loader数据加载器(值为函数),它会在在导航过程中启动数据加载。
|
|
|
|
+2. 在路由组件中,通过`useLoaderData()`Hook函数来获取上面loader返回的数据
|
|
|
|
+
|
|
|
|
+## 7. 页面访问权限
|
|
|
|
+
|
|
|
|
+## 8. 其他
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|