route-ui-sync
v0.1.2
Published
A lightweight, framework-agnostic utility for automatically matching routes to menu keys. Zero dependencies, TypeScript support, for navigation menus.
Downloads
33
Maintainers
Readme
route-ui-sync
一个与 UI 框架无关的工具库,用于根据当前路由信息计算选中 key。
应用场景
适用于菜单选中、面包屑导航、标签页激活、侧边栏高亮等需要根据路由状态决定 UI 状态的场景,处理路由和 UI 状态不匹配的问题。
特性
- 🎯 框架无关:不依赖 React、Vue 或任何路由库
- 🚀 极轻量级:零依赖,压缩后仅 ~314 字节(ES 模块)或 ~321 字节(CommonJS)
- 🔧 类型安全:完整的 TypeScript 类型定义
- ✅ 测试覆盖:完整的单元测试
- 📦 多格式支持:同时支持 ES 模块和 CommonJS
- ⚡ 高度优化:使用 Terser 进行激进压缩,生产环境不包含 sourcemap
安装
npm install route-ui-sync快速开始
3 步完成菜单自动选中
// 1. 导入函数和类型
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
import { useLocation } from 'react-router-dom';
// 2. 定义规则:哪些路由对应哪些菜单 key
const rules: RouteSelectionRule[] = [
{ key: 'page1', match: (loc) => loc.pathname === '/page1' },
{ key: 'page2', match: (loc) => loc.pathname === '/page2' },
];
// 3. 获取当前路由,自动计算选中的 key
const location = useLocation();
const selectedKey = getSelectedKeyFromLocation(location, rules);
// 使用 selectedKey 来高亮菜单
<Menu selectedKeys={selectedKey ? [selectedKey] : []} />使用方法
基本用法
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
// 定义路由匹配规则
const rules: RouteSelectionRule[] = [
{
key: 'page1',
match: (location) => location.pathname === '/page1',
},
{
key: 'page2',
match: (location) => location.pathname === '/page2',
},
{
key: 'items',
match: (location) => location.pathname.startsWith('/items'),
},
];
// 根据当前 location 计算选中的 key
const location = { pathname: '/page1' };
const selectedKey = getSelectedKeyFromLocation(location, rules);
console.log(selectedKey); // 'page1'使用优先级
当多个规则同时匹配时,可以使用 priority 属性来控制优先级:
const rules: RouteSelectionRule[] = [
{
key: 'item-detail',
match: (location) => /^\/items\/\d+$/.test(location.pathname),
priority: 10, // 高优先级
},
{
key: 'items',
match: (location) => location.pathname.startsWith('/items'),
priority: 5, // 低优先级
},
];
const location = { pathname: '/items/123' };
const selectedKey = getSelectedKeyFromLocation(location, rules);
console.log(selectedKey); // 'item-detail' (因为优先级更高)处理查询参数和哈希
const rules: RouteSelectionRule[] = [
{
key: 'search',
match: (location) => {
return location.pathname === '/search' && location.search?.includes('q=');
},
},
{
key: 'section',
match: (location) => location.hash === '#section1',
},
];
const location = {
pathname: '/search',
search: '?q=test',
hash: '#section1',
};
const selectedKey = getSelectedKeyFromLocation(location, rules);与 React Router 集成
基础示例
import { useLocation } from 'react-router-dom';
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
import { Menu } from '@arco-design/web-react';
function NavigationMenu() {
const location = useLocation();
// 定义菜单选中规则
const rules: RouteSelectionRule[] = [
{
key: 'page1',
match: (loc) => loc.pathname === '/page1',
},
{
key: 'page2',
match: (loc) => loc.pathname === '/page2',
},
{
key: 'items',
match: (loc) => loc.pathname.startsWith('/items'),
},
];
// 根据当前路由自动计算选中的菜单项
const selectedKey = getSelectedKeyFromLocation(location, rules);
return (
<Menu selectedKeys={selectedKey ? [selectedKey] : []}>
<Menu.Item key="page1">页面一</Menu.Item>
<Menu.Item key="page2">页面二</Menu.Item>
<Menu.Item key="items">项目列表</Menu.Item>
</Menu>
);
}实际项目示例:动态菜单规则
在实际项目中,你可能需要根据动态数据(如从 API 获取的产品分类)来生成规则:
import { useLocation } from 'react-router-dom';
import { useMemo } from 'react';
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
import { Menu } from '@arco-design/web-react';
interface Category {
categoryId: string;
name: string;
}
function NavigationMenu() {
const location = useLocation();
const [categories, setCategories] = useState<Category[]>([]);
// 从 API 获取分类
useEffect(() => {
fetchCategories().then(setCategories);
}, []);
// 动态生成菜单选中规则
const menuSelectionRules: RouteSelectionRule[] = useMemo(() => {
const rules: RouteSelectionRule[] = [
{
key: 'page1',
match: (loc) => loc.pathname === '/page1',
},
{
key: 'page2',
match: (loc) => loc.pathname === '/page2',
},
];
// 根据分类动态添加规则
// 例如:/item?type=category1 对应 item-1
categories.forEach((cat, index) => {
const key = `item-${index + 1}`;
rules.push({
key,
match: (loc) => {
if (loc.pathname !== '/item') return false;
const params = new URLSearchParams(loc.search ?? '');
return params.get('type') === cat.categoryId;
},
});
});
return rules;
}, [categories]);
// 自动计算当前选中的菜单项
const selectedMenuKey = useMemo(
() => getSelectedKeyFromLocation(location, menuSelectionRules),
[location, menuSelectionRules]
);
return (
<Menu selectedKeys={selectedMenuKey ? [selectedMenuKey] : []}>
<Menu.Item key="page1">页面一</Menu.Item>
<Menu.Item key="page2">页面二</Menu.Item>
{categories.map((cat, index) => (
<Menu.Item key={`item-${index + 1}`}>
{cat.name}
</Menu.Item>
))}
</Menu>
);
}使用优先级处理复杂场景
当路由有嵌套关系时,使用优先级确保精确匹配:
const rules: RouteSelectionRule[] = [
// 精确匹配:项目详情页
{
key: 'item-detail',
match: (loc) => /^\/items\/\d+$/.test(loc.pathname),
priority: 10, // 高优先级
},
// 模糊匹配:项目列表页
{
key: 'items',
match: (loc) => loc.pathname.startsWith('/items'),
priority: 5, // 低优先级
},
];
// 访问 /items/123 时,会选中 'item-detail'(优先级更高)
// 访问 /items 时,会选中 'items'API
getSelectedKeyFromLocation
根据当前路由信息和一组规则,计算"当前应该选中的 key"。
参数
location: 包含pathname、search(可选)、hash(可选)的对象rules: 路由选择规则数组
返回值
string | undefined: 匹配的 key,如果没有匹配则返回undefined
RouteSelectionRule
路由选择规则接口。
interface RouteSelectionRule {
/**
* 唯一 key,用于传给 UI 组件
*/
key: string;
/**
* 匹配函数:根据当前 location 判断这个规则是否命中
*/
match: RouteMatchPredicate;
/**
* 优先级:数字越大优先级越高(可选)
* 当多个规则同时命中时,用它来决定最终选中哪个
*/
priority?: number;
}RouteMatchPredicate
匹配函数类型。
type RouteMatchPredicate = (location: {
pathname: string;
search?: string;
hash?: string;
}) => boolean;开发
构建
npm run build测试
项目使用 Vitest 进行单元测试,包括源代码测试和打包后代码测试。
# 运行源代码测试
npm test
# 监听模式运行测试
npm run test:watch
# 运行测试并生成覆盖率报告
npm run test:coverage
# 测试打包后的代码(自动构建并测试)
npm run test:bundle
# 运行所有测试(源代码 + 打包后代码)
npm run test:all类型检查
npm run type-check测试覆盖
项目包含完整的单元测试,覆盖了各种使用场景:
- 测试用例数: 39 个(源代码测试)+ 22 个(打包后代码测试)= 61 个测试用例
- 代码覆盖率: 100%
- 测试场景: 包括基础功能、复杂场景、边界情况、实际项目场景等
- 打包验证: 自动测试打包后的 ES 模块和 CommonJS 模块功能
打包后代码测试说明:
test:bundle 命令会:
- 自动构建项目(
npm run build) - 测试打包后的 ES 模块(
dist/index.js) - 测试打包后的 CommonJS 模块(
dist/index.cjs) - 验证打包文件大小和完整性
这确保了打包后的代码功能与源代码完全一致,验证了压缩和优化过程没有破坏功能。
查看 TEST_COVERAGE.md 了解详细的测试覆盖情况。
更多示例
查看 USAGE_EXAMPLES.md 获取更多详细的使用示例,包括:
- 基础使用场景
- React Router 集成
- 动态规则生成
- 优先级控制
- 处理查询参数
- 完整项目示例
许可证
MIT
