React框架配套的路由系统插件 是 React Router。目前版本为V6。
createBroswerRouter或者createHashRouter
引入RouterProvider组件
定义相关路由组件
// 定义路由的模块 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;
// 入口文件
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步走:
在父路由组件上通过Outlet组件来指定子路由组件的渲染位置
// 定义嵌套子路由
const routes = [
{
path: '/',
element: <Layout />,
children: [
// 这是索引路由
{
// path: '',
index: true,
element: <Home />,
},
{
path: 'about',
element: <About />,
},
],
},
];
// 父组件 -- 布局组件
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;
当实现一个类似详情页的需求时,我们可能会需要使用到动态路由
例如,在商品列表页中点击某一商品获取详情。
/products
/products/details/{product-id}
因此这就是动态路由。{path: '/products/details/:productId'}
通过路由Hook useParams
来获取上面解析后的路由参数对象
// 列表页代码
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;
在实际开发中,可以通过路由参数实现页面间数据传递;当然也可以通过查询参数。
下面代码演示,如果通过查询参数实现页面间数据传递:
<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>
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>
NavLink在使用时,更容易实现选中效果。
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>
);
}
通过 useNavigate()
Hook 获取到 navigate
函数,接着通过调用navigate函数实现页面跳转。
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: {}})
在路由系统中,如何选择时机去获取数据?
我们知道在VueRouter中,已经提供了两种方式,都是用户能够接收并且体验良好的方案
- 在组件渲染后,添加Loading效果后去获取数据
- 在路由开启导航时先去获取数据,这样组件渲染时就会连同数据一起渲染出来了
useLoaderData()
Hook函数来获取上面loader返回的数据Vue Router中可以通过导航守卫钩子来拦截到所有的页面导航,根据当前用户权限决定是否继续导航。
但是React Router需要根据不同的路由定义方式来自行实现。
/**
*! 自定义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]);
}
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
export default function Protected() {
const navigate = useNavigate();
// 页面访问鉴权
useAuth();
return (
<div className="Protected">
<h3>受保护页面——需要登录后才可以访问</h3>
<div>
<button
onClick={() => {
if (window.confirm('确定退出系统吗')) {
localStorage.clear();
navigate('/login');
}
}}
>
退出登录
</button>
</div>
</div>
);
}
为了提高React项目首次加载速度,推荐使用路由懒加载。
实现时,需要从React核心库中引入lazy
函数和Suspense
组件。像这样,
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: (
<Suspense fallback={<h3>加载中...</h3>}>
<About />
</Suspense>
),
},
]);
export default router;
在定义路由时,React Router还提供了一系列组件:
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 (
<Suspense fallback={<h3>组件疯狂加载中...</h3>}>
<Component />
</Suspense>
);
}
function App() {
return (
<div className="App">
<header className="App-header">
<NavLink className="App-link" to={'/'}>
主页
</NavLink>{' '}
<NavLink className="App-link" to={'/about'}>
关于
</NavLink>
<NavLink className="App-link" to={'/user'}>
用户
</NavLink>
</header>
<main>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={withSuspense(About)} />
<Route path="/user" element={withSuspense(User)}>
<Route index />
<Route path="profile" element={<UserProfile />} />
<Route
path="post"
// 并不会起作用。它只能在RouterProvider API中使用
lazy={() => import('./pages/UserPost.jsx')}
element={<UserPost />}
/>
</Route>
<Route path="/list/:lid" />
</Routes>
</main>
</div>
);
}
export default App;
其中,Route
组件就是定义路由的。当其path属性和当前地址匹配成功后,就会在其位置下渲染element绑定的React元素。
同时可以定义多个Route
组件,这样就可以在页面中定义多个路由。并且这些Route
组件必须作为Routes
组件的直接子代元素。
为了将路由功能注入到项目中,需要在App组件中将整个项目代码包裹在一个BrowserRouter
或者HashRouter
组件中。