本指南涵盖了将 @esmx/router 与 React 集成的全部内容。与 Vue 不同,React 不需要单独的集成包 —— 路由内置的微应用系统直接处理挂载、卸载和服务端渲染。
只需安装核心路由包:
npm install @esmx/router不需要额外的集成包。React 通过路由的 apps 回调工作,该回调提供 mount、unmount 和 renderToString 生命周期钩子。
React 集成使用微应用模式:
apps 回调 — 告诉路由如何挂载、卸载和渲染你的 React 应用mount(el) — 创建 React 根节点并将应用渲染到 DOM 元素中unmount(el, root) — 卸载 React 根节点进行清理renderToString() — 将应用服务端渲染为 HTML 字符串路由将自身传递给 apps 回调,因此你的 React 组件可以通过 props 或 React context 访问它。
路由是框架无关的 —— 与 Vue 相同:
import type { RouteConfig } from '@esmx/router';
export const routes: RouteConfig[] = [
{
path: '/',
component: () => import('./layouts/MainLayout'),
children: [
{ path: '', component: () => import('./pages/Home') },
{ path: 'about', component: () => import('./pages/About') },
{
path: 'users/:id',
component: () => import('./pages/UserProfile'),
meta: { requiresAuth: true }
}
]
}
];设置 React context,使任何组件都能访问路由:
import { createContext, useContext, useState, useEffect } from 'react';
import type { Router, Route } from '@esmx/router';
interface RouterContextValue {
router: Router;
route: Route;
}
const RouterContext = createContext<RouterContextValue | null>(null);
export function RouterProvider({
router,
children
}: {
router: Router;
children: React.ReactNode;
}) {
const [route, setRoute] = useState(router.route);
useEffect(() => {
return router.afterEach((to) => {
setRoute(to);
});
}, [router]);
return (
<RouterContext.Provider value={{ router, route }}>
{children}
</RouterContext.Provider>
);
}
export function useRouter(): Router {
const context = useContext(RouterContext);
if (!context) {
throw new Error('useRouter 必须在 RouterProvider 内部使用');
}
return context.router;
}
export function useRoute(): Route {
const context = useContext(RouterContext);
if (!context) {
throw new Error('useRoute 必须在 RouterProvider 内部使用');
}
return context.route;
}构建渲染匹配路由组件的应用根组件:
import { RouterProvider, useRoute } from './router-context';
import type { Router } from '@esmx/router';
function AppContent() {
const route = useRoute();
const Component = route.matched[0]?.component;
return Component ? <Component /> : <div>未找到页面</div>;
}
export default function App({ router }: { router: Router }) {
return (
<RouterProvider router={router}>
<AppContent />
</RouterProvider>
);
}使用 router.resolveLink() 构建导航链接组件:
import type { ReactNode, MouseEvent } from 'react';
import { useRouter } from '../router-context';
interface RouterLinkProps {
to: string;
activeClass?: string;
className?: string;
children: ReactNode;
}
export function RouterLink({ to, activeClass, className, children }: RouterLinkProps) {
const router = useRouter();
const link = router.resolveLink({ to, activeClass });
function handleClick(e: MouseEvent) {
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
e.preventDefault();
router.push(to);
}
return (
<a
href={link.attributes.href}
className={[className, link.attributes.class].filter(Boolean).join(' ')}
onClick={handleClick}
>
{children}
</a>
);
}客户端入口使用 apps 回调创建路由,并使用 createRoot 进行挂载:
import { Router, RouterMode } from '@esmx/router';
import { createRoot } from 'react-dom/client';
import { createElement } from 'react';
import App from './App';
import { routes } from './routes';
const router = new Router({
root: '#app',
mode: RouterMode.history,
routes,
apps: (router) => ({
mount(el) {
const root = createRoot(el);
root.render(createElement(App, { router }));
return root;
},
unmount(el, root) {
root.unmount();
},
async renderToString() {
const { renderToString } = await import('react-dom/server');
return renderToString(createElement(App, { router }));
}
})
});apps 回调说明:
mount(el) — 当路由需要渲染应用时调用。返回值(React 根节点)将传递给后续的 unmount。unmount(el, root) — 当应用需要销毁时调用。renderToString() — 在服务端进行 SSR 时调用。返回 HTML 字符串。import type { RenderContext } from '@esmx/core';
import { Router, RouterMode } from '@esmx/router';
import { createElement } from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';
import { routes } from './routes';
export default async (rc: RenderContext) => {
const router = new Router({
mode: RouterMode.memory,
base: new URL(rc.params.url, 'http://localhost'),
routes,
apps: (router) => ({
mount(el) { /* 仅客户端 */ },
unmount(el) { /* 仅客户端 */ },
async renderToString() {
return renderToString(createElement(App, { router }));
}
})
});
await router.replace(rc.params.url);
const html = await router.renderToString();
rc.html = `<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
${rc.preload()}
${rc.css()}
</head>
<body>
<div id="app">${html}</div>
${rc.importmap()}
${rc.moduleEntry()}
${rc.modulePreload()}
</body>
</html>`;
};使用 @esmx/rspack-react 中的 createRspackReactApp 进行 React 专属的构建工具配置:
import http from 'node:http';
import type { EsmxOptions } from '@esmx/core';
export default {
async devApp(esmx) {
return import('@esmx/rspack-react').then((m) =>
m.createRspackReactApp(esmx)
);
},
async server(esmx) {
const server = http.createServer((req, res) => {
esmx.middleware(req, res, async () => {
const rc = await esmx.render({
params: { url: req.url }
});
res.end(rc.html);
});
});
server.listen(3000, () => {
console.log('Server started: http://localhost:3000');
});
}
} satisfies EsmxOptions;createRspackReactApp 配置了 Rspack,支持 JSX/TSX、React Refresh HMR 和 SSR 打包。
使用 useRouter hook 进行编程式导航:
import { useRouter, useRoute } from '../router-context';
export default function UserProfile() {
const router = useRouter();
const route = useRoute();
const userId = route.params.id;
return (
<div>
<h1>用户 {userId}</h1>
<p>当前路径:{route.path}</p>
<p>查询参数:{JSON.stringify(route.query)}</p>
<button onClick={() => router.push('/')}>
返回首页
</button>
<button onClick={() => router.replace('/about')}>
替换为关于页
</button>
<button onClick={() => router.back()}>
后退
</button>
</div>
);
}import { RouterLink } from '../components/RouterLink';
import { useRoute } from '../router-context';
export default function MainLayout() {
const route = useRoute();
// 从匹配的路由渲染子组件
const ChildComponent = route.matched[1]?.component;
return (
<div>
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<RouterLink to="/users/42" activeClass="nav-active">
用户 42
</RouterLink>
</nav>
<main>
{ChildComponent ? <ChildComponent /> : null}
</main>
</div>
);
}import { useRoute } from '../router-context';
function MyComponent() {
const route = useRoute();
// 当前路径
route.path // '/users/42'
// 路由参数
route.params // { id: '42' }
// 查询字符串参数
route.query // { tab: 'profile' }
// 路由元信息
route.meta // { requiresAuth: true }
// 匹配的路由配置(从父到子)
route.matched // RouteConfig[]
}典型的 React + SSR 项目结构(使用 @esmx/router):
src/
├── entry.node.ts # Node.js 服务器配置、开发/构建设置
├── entry.server.tsx # SSR 渲染逻辑
├── entry.client.tsx # 客户端挂载
├── router-context.tsx # 路由的 React context(useRouter、useRoute)
├── routes.ts # 路由定义
├── App.tsx # 根组件
├── components/
│ └── RouterLink.tsx # 导航链接组件
├── layouts/
│ └── MainLayout.tsx # 带导航的布局
└── pages/
├── Home.tsx
├── About.tsx
└── UserProfile.tsx