maishu-chitu-react
v2.0.0
Published
用 chitu 替换 react-router 的兼容层(BrowserRouter / Routes / Route / Outlet / Link / hooks)
Readme
maishu-chitu-react
用 chitu.js 替换 react-router 的兼容层:
业务代码里 import { ... } from 'react-router' / 'react-router-dom' 基本不用改,
底层路由与页面管理由 chitu 的 PageMaster 承担。
兼容层实现的是目标项目实际用到的 react-router 子集(见下方「已实现 / 未实现」), 并非 react-router v6 的全部 API;
path="*"这类写法需按本文的模板语法调整。
自本版本起,本包只提供 react-router 兼容层。 不再导出 chitu 原生用法的
Application/PageProps/dataLoader—— 需要旧 API 的项目请锁定旧版本。
特性
- 声明式路由零改动:
<BrowserRouter>/<Routes>/<Route>/<Outlet>/<Link>/<NavLink>与 react-router 同形。 - 真正的嵌套 Outlet:每个
<Outlet>挂一个 chituPageMaster,布局外壳始终挂载,只有子页面切换显隐。 - 干净的 URL:地址栏就是
/admin/users这类 pathname,不会被改写成 chitu 的 pageName 形式;一次导航 = 一条 history 记录。 - 页面级缓存与卸载:chitu 按路由缓存页面(
display:none切换),页面关闭时自动root.unmount()清理副作用。 - 路径模板与 node-shop-v2 同源:底层用
path-to-regexp(express 风格),支持:id、花括号可选段{/:page}、/:page.css、{*splat}通配等。 - 单一产物:整个包只有一份实现(
dist/index.*),不存在多入口各带一份 context 的隐患。
安装
npm install maishu-chitu-react maishu-chitu react react-dom
maishu-chitu(>=5.0.0)、react/react-dom(>=18.3)为对等依赖。 兼容层本身不依赖maishu-chitu的Application,只使用PageMaster。
接入
// 直接用包名,不再需要 react-router / react-router-dom
import { BrowserRouter, Routes, Route, Outlet, Link, NavLink } from 'maishu-chitu-react';旧项目迁移时把说明符批量替换即可(from "react-router" / from "react-router-dom" → from "maishu-chitu-react")。
用 importmap 的项目只需映射 maishu-chitu-react 与 maishu-chitu 两项。
.、./react-router、./react-router-dom三个入口都指向同一份dist/index.*, 混用也不会出现「两份 React context」的问题(因此maishu-chitu-react/react-router这类旧写法仍可用)。
用法
import {
BrowserRouter, Routes, Route, Outlet,
Link, NavLink,
useParams, useLocation, useNavigate, useSearchParams,
generatePath,
} from 'maishu-chitu-react';
function Layout() {
return (
<div>
<NavLink to="/">首页</NavLink>
<NavLink to="/products">商品</NavLink>
{/* 子路由渲染在这里 —— 布局本身不会被重新挂载 */}
<Outlet />
</div>
);
}
createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="products" element={<ProductList />} />
<Route path="products/:id" element={<ProductDetail />} />
<Route path="admin" element={<AdminLayout />}>
<Route index element={<AdminHome />} />
<Route path="users" element={<AdminUsers />} />
</Route>
{/* catch-all:path-to-regexp 的通配写法(不支持 react-router 的裸 `*`) */}
<Route path="*splat" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>,
);已实现 / 未实现
已实现(即目标项目当前使用的全部符号):
| 类型 | API |
| --- | --- |
| 组件 | BrowserRouter Routes Route Outlet Link NavLink |
| Hooks | useParams useLocation useNavigate useSearchParams |
| 函数 | generatePath |
| 类型 | LinkProps NavLinkProps NavigateFunction Location |
未实现(v6 的其余 API):Navigate、useRoutes、useMatch、createBrowserRouter /
RouterProvider、MemoryRouter、HashRouter、useOutletContext、useNavigationType、Prompt 等。
用到这些的业务代码需要自行改造。
需要改写的写法:catch-all 的 <Route path="*" /> 在本兼容层里要写成 <Route path="*splat" />
(通配内容读 useParams().splat;若需连 / 也命中则写 "{*splat}")——
因为底层是 path-to-regexp,它不接受 react-router 的裸 *。
行为差异:useLocation() 的 hash / state / key 为近似实现;generatePath 默认对参数值做
encodeURIComponent(react-router v6 不编码)。
路由模板(path-to-regexp 语法)
| 写法 | 含义 |
| --- | --- |
| /product/:id | 动态段,useParams().id |
| /product-list{/:pageIndex} | 可选段(无值时整段省略) |
| /{home} | 可选字面量段(既匹配 / 也匹配 /home) |
| /{:page} | 花括号内的参数 |
| /:page.css | 带字面量后缀的参数 |
| *splat | 通配(catch-all),至少一段,useParams().splat 读取内容 |
| {*splat} | 通配且可为空 —— 等价 react-router 的 *(能匹配 / 本身),做 404 兜底请用这个 |
模板必须是 path-to-regexp v8 语法:不支持 react-router 的
:id?(写{/:id})与裸*(写{*splat}; 注意*splat是「必填」,根路径不会命中,别拿它当 404 兜底)。 迁移已有项目时把这些写法改掉即可;服务端若也用 v8(如本项目配套的 node-shop-v2),两边模板可保持一致。
参数生成(
generatePath)与匹配同源:必填段缺值抛错,通配段按/切分后拼接。
页面生命周期与性能
- 页面按路由缓存(同一路由换参数不会新建节点),DOM 节点数 = 路由种类数。
- 隐藏页面通过
display:none切换,不参与布局与绘制,仅占用内存。 - 页面关闭时 React
root会unmount(),组件卸载并清理副作用。 - 隐藏(未关闭)页面的 React 副作用仍可能后台运行,请在组件
useEffect中做好清理。
开发
npm run build # esbuild 打包 dist(cjs + mjs)+ tsc 生成类型声明
npm run start # 启动本地 demo(PORT 环境变量,默认 8080)
npm test # jest 运行测试(jsdom 环境)demo 见
demo/rr-main.tsx:两级嵌套 Outlet、动态参数、query 读写、返回、catch-all, 面板会显示外壳挂载时间戳与浏览器地址栏,用于验证「外壳保持挂载 / 地址栏未被改写」。
