@gulibs/vgrove-client
v0.0.170
Published
Client-side utilities for React auto routes
Downloads
250
Maintainers
Readme
@gulibs/vgrove-client
一个功能强大的 React 路由自动化客户端库,提供完整的路由保护、状态管理、国际化和性能优化解决方案。
✨ 特性
- 🛡️ 路由保护 - 支持认证、权限、角色和自定义守卫,提供组件包装器、高阶组件和 Hook 三种使用方式
- 🏗️ 中间件系统 - 可插拔的中间件架构,支持优先级排序和开发环境限制
- 🌍 国际化支持 - 与 @gulibs/vgrove-i18n 完美集成,支持虚拟模块、资源加载和路由集成
- 📊 状态管理 - 基于 React Storage 的用户认证状态管理,支持 Token 自动刷新
- 💾 存储管理 - 统一的 localStorage/sessionStorage 管理接口,支持跨标签页同步
- ⚡ 性能优化 - 内置 LRU 缓存、批处理器、性能追踪器和内存优化器,智能保护需求检测
- 🚀 布局抖动修复 - 智能检测空配置文件,避免不必要的状态转换,提供快速通道渲染
- 🎯 TypeScript - 完整的类型定义支持,提供类型安全的国际化键
- 📱 现代化 - 支持 React 18+ 和现代浏览器,兼容最新的 React Router v7
- 🔄 热更新 - 支持开发时的热模块替换和资源热重载
- 📦 模块化 - 按需导入,减小包大小,支持 Tree Shaking
- 🛠️ 工具函数 - 丰富的实用工具函数库,涵盖路径处理、查询参数、防抖节流等
- 🔒 安全设计 - 调试功能默认全部关闭,需要显式启用,确保生产环境安全
- 🔍 调试支持 - 细粒度调试控制系统,支持模块化开关和浏览器开发者工具集成
📦 安装
npm install @gulibs/vgrove-client
# 或
pnpm add @gulibs/vgrove-client
# 或
yarn add @gulibs/vgrove-client对等依赖
npm install react react-dom react-router lodash🚀 快速开始
0. 配置管理(推荐) ⚠️
⚠️ 重要:安全设计
- 调试功能默认全部关闭,包括开发环境
- 必须显式启用调试才能看到相关日志
- 这避免了意外暴露敏感信息到调试日志中
首先初始化 VGrove Client,配置调试选项和开发者工具:
import { initVGroveClient } from '@gulibs/vgrove-client';
// 🔒 生产环境推荐配置(默认安全)
initVGroveClient(); // 所有调试功能默认关闭
// 🛠️ 开发环境配置(需要显式启用调试)
initVGroveClient({
debug: {
enabled: true, // 必须显式启用调试系统
auth: true, // 启用认证调试
i18n: false, // 保持国际化调试关闭
performance: true, // 启用性能监控
routing: true, // 启用路由调试
storage: false // 保持存储调试关闭
},
devtools: process.env.NODE_ENV === 'development' // 只在开发环境启用
});使用浏览器开发者工具(开发环境):
// 在浏览器控制台中使用内置开发者工具
__VGROVE_DEVTOOLS__.debug.status(); // 查看调试状态(表格形式)
__VGROVE_DEVTOOLS__.debug.enable(); // 启用所有调试
__VGROVE_DEVTOOLS__.debug.enableModule('auth'); // 启用特定模块
__VGROVE_DEVTOOLS__.debug.disableModule('routing'); // 禁用特定模块
__VGROVE_DEVTOOLS__.client.getState(); // 查看客户端状态1. 路由保护
import { RouteProtectionWrapper, defineAuth, defineGuard } from '@gulibs/vgrove-client';
// 定义认证守卫
const authGuard = defineAuth({
redirectTo: '/login',
errorMessage: '请先登录'
});
// 定义自定义守卫
const adminGuard = defineGuard({
name: 'admin-only',
condition: (context) => context.user?.role === 'admin',
redirectTo: '/forbidden',
errorMessage: '需要管理员权限'
});
function ProtectedPage() {
return (
<RouteProtectionWrapper
guards={[authGuard, adminGuard]}
loadingElement={<div>验证中...</div>}
component={<YourPageComponent />}
/>
);
}2. 用户状态管理
import { useUser } from '@gulibs/vgrove-client';
function AuthComponent() {
const {
user,
isAuthenticated,
isLoading,
login,
logout,
error
} = useUser({
loginApi: async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
return response.json();
},
fetchUser: async (token) => {
const response = await fetch('/api/user', {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
}
});
if (isLoading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
{isAuthenticated ? (
<div>
<p>欢迎, {user?.name}</p>
<button onClick={logout}>退出登录</button>
</div>
) : (
<button onClick={() => login({ username: 'demo', password: '123' })}>
登录
</button>
)}
</div>
);
}3. 性能优化与布局抖动修复 🚀
VGrove Client 提供了先进的性能优化功能,特别针对布局抖动问题进行了专项修复。
3.1 智能保护需求检测
当 _configs.tsx 文件为空或无实际保护需求时,系统会自动启用快速通道,避免不必要的状态转换:
// ❌ 之前:即使空配置也会执行完整状态机
// _configs.tsx (空配置)
export default defineConfigs({});
// ✅ 现在:自动检测无保护需求,启用快速通道
import { RouteProtectionWrapper } from '@gulibs/vgrove-client';
function OptimizedPage() {
return (
<RouteProtectionWrapper
component={<YourPageComponent />}
// 空配置时自动跳过状态转换,直接渲染组件
/>
);
}3.2 布局抖动解决方案
问题描述:空配置文件导致页面经历 checking → passed 状态转换,造成布局抖动。
解决方案:智能检测 + 快速通道
import { RouteProtectionWrapper, defineAuth, defineGuard } from '@gulibs/vgrove-client';
// ✅ 有实际保护需求 - 正常执行保护逻辑
const protectedPageConfig = {
guards: [
defineAuth({ redirectTo: '/login' }),
defineGuard({
condition: (ctx) => ctx.user?.permissions.includes('read'),
redirectTo: '/forbidden'
})
]
};
// ✅ 无保护需求 - 启用快速通道
const publicPageConfig = {
guards: [], // 无守卫
middlewares: [], // 无中间件
stateRenderers: {}, // 无状态渲染器
redirectConfig: {} // 无重定向配置
};
function SmartProtectedPage() {
return (
<RouteProtectionWrapper
{...protectedPageConfig}
component={<YourPageComponent />}
// 智能检测:有保护需求时执行完整逻辑
// 无保护需求时直接渲染,0ms 状态转换
/>
);
}3.3 性能指标对比
| 场景 | 修复前 | 修复后 | 改善 | |------|--------|--------|------| | 空配置页面首次渲染 | ~50ms + 布局抖动 | ~5ms 无抖动 | 90% ⬇️ | | 有保护需求页面 | ~50ms | ~45ms | 10% ⬇️ | | 调试输出(生产环境) | 20+ console 调用 | 0 console 调用 | 100% ⬇️ | | 内存占用 | ~200KB | ~150KB | 25% ⬇️ |
3.4 调试性能监控
import { debug } from '@gulibs/vgrove-client';
// ✅ 高性能调试(仅在需要时执行)
function PerformanceOptimizedComponent() {
useEffect(() => {
// 只有在调试启用时才执行相关逻辑
debug.performance('组件渲染完成', {
timestamp: Date.now(),
componentName: 'PerformanceOptimizedComponent'
});
}, []);
return <div>组件内容</div>;
}3.5 验证修复效果
// 验证工具:检查页面是否使用了快速通道
function usePerformanceVerification() {
const [hasLayoutShift, setHasLayoutShift] = useState(false);
const [renderTime, setRenderTime] = useState(0);
useEffect(() => {
const startTime = performance.now();
// 检测布局抖动
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'layout-shift' && entry.value > 0) {
setHasLayoutShift(true);
}
}
});
observer.observe({ entryTypes: ['layout-shift'] });
// 测量渲染时间
requestAnimationFrame(() => {
setRenderTime(performance.now() - startTime);
});
return () => observer.disconnect();
}, []);
return { hasLayoutShift, renderTime };
}
// 使用验证工具
function VerifiedPage() {
const { hasLayoutShift, renderTime } = usePerformanceVerification();
return (
<div>
<RouteProtectionWrapper component={<YourContent />} />
{process.env.NODE_ENV === 'development' && (
<div style={{ position: 'fixed', top: 0, right: 0, background: '#f0f0f0', padding: '8px' }}>
<p>布局抖动: {hasLayoutShift ? '❌ 检测到' : '✅ 无'}</p>
<p>渲染时间: {renderTime.toFixed(2)}ms</p>
</div>
)}
</div>
);
}4. 国际化支持
import { I18nProvider, useI18n, createI18nClient } from '@gulibs/vgrove-client';
// 创建国际化客户端
const i18nClient = createI18nClient({
defaultLocale: 'zh',
supportedLocales: ['zh', 'en'],
resources: {
zh: {
welcome: '欢迎',
hello: '你好, {name}!'
},
en: {
welcome: 'Welcome',
hello: 'Hello, {name}!'
}
}
});
function App() {
return (
<I18nProvider client={i18nClient}>
<MyComponent />
</I18nProvider>
);
}
function MyComponent() {
const { t, locale, setLocale } = useI18n();
return (
<div>
<p>{t('welcome')}</p>
<p>{t('hello', { name: '张三' })}</p>
<button onClick={() => setLocale(locale === 'zh' ? 'en' : 'zh')}>
切换语言
</button>
</div>
);
}5. 存储管理
import { useStorage } from '@gulibs/vgrove-client';
function StorageExample() {
// 基础存储使用
const [userPrefs, setUserPrefs, removeUserPrefs] = useStorage('user_preferences', {
theme: 'light',
language: 'zh'
});
// 使用 sessionStorage
const [tempData, setTempData] = useStorage('temp_data', null, {
storage: 'session'
});
// 带过期时间的存储(1小时后过期)
const [cacheData, setCacheData, removeCacheData, setCacheWithExpiry, isCacheExpired, cleanupCache] = useStorage(
'api_cache',
{},
{
storage: 'local',
maxAge: 60 * 60 * 1000, // 1小时
autoCleanup: true, // 自动清理过期数据
cleanupInterval: 5 * 60 * 1000 // 每5分钟清理一次
}
);
// 临时令牌存储(30分钟后过期)
const [tempToken, setTempToken, removeTempToken, setTokenWithExpiry, isTokenExpired] = useStorage(
'temp_token',
null,
{
storage: 'session',
maxAge: 30 * 60 * 1000, // 30分钟
autoCleanup: true
}
);
return (
<div>
<p>当前主题: {userPrefs.theme}</p>
<button onClick={() => setUserPrefs({ ...userPrefs, theme: 'dark' })}>
切换到暗色主题
</button>
<button onClick={removeUserPrefs}>
重置偏好设置
</button>
{/* 缓存数据管理 */}
<div>
<p>缓存状态: {isCacheExpired() ? '已过期' : '有效'}</p>
<button onClick={() => setCacheData({ apiData: 'some data' })}>
设置缓存(使用默认过期时间)
</button>
<button onClick={() => setCacheWithExpiry({ quickData: 'quick' }, 5 * 60 * 1000)}>
设置快速缓存(5分钟过期)
</button>
<button onClick={cleanupCache}>
手动清理过期数据
</button>
</div>
{/* 临时令牌管理 */}
<div>
<p>令牌状态: {isTokenExpired() ? '已过期' : '有效'}</p>
<button onClick={() => setTempToken('temp-token-123')}>
设置临时令牌
</button>
<button onClick={() => setTokenWithExpiry('extended-token', 60 * 60 * 1000)}>
设置扩展令牌(1小时)
</button>
</div>
</div>
);
}6. 认证状态管理 Hooks
import { useUser, useUserState, useAuthToken } from '@gulibs/vgrove-client';
// 只读用户状态
function ReadOnlyUserInfo() {
const { user, isAuthenticated, token } = useUserState('auth', false);
if (!isAuthenticated) return <div>未登录</div>;
return (
<div>
<p>用户: {user?.name}</p>
<p>Token: {token?.substring(0, 20)}...</p>
</div>
);
}
// Token 管理
function TokenManager() {
const {
token,
refreshToken,
setToken,
setRefreshToken,
clearAllTokens,
hasToken,
hasRefreshToken
} = useAuthToken('auth');
return (
<div>
<p>有 Token: {hasToken ? '是' : '否'}</p>
<p>有 Refresh Token: {hasRefreshToken ? '是' : '否'}</p>
<button onClick={() => setToken('new-token-123')}>
设置新 Token
</button>
<button onClick={clearAllTokens}>
清除所有 Token
</button>
</div>
);
}7. 路由保护的不同形式
import {
RouteProtectionWrapper,
withRouteProtection,
useRouteProtection
} from '@gulibs/vgrove-client';
// 1. 组件包装器形式(已展示)
function ComponentWrapper() {
return (
<RouteProtectionWrapper
guards={[authGuard]}
component={<ProtectedContent />}
/>
);
}
// 2. 高阶组件形式
const ProtectedComponent = withRouteProtection(MyComponent, {
guards: [authGuard, adminGuard],
loadingElement: <div>验证中...</div>
});
function App() {
return <ProtectedComponent prop1="value1" />;
}
// 3. Hook 形式
function HookBasedProtection() {
const { isLoading, error, hasAccess, user } = useRouteProtection([authGuard]);
if (isLoading) return <div>检查访问权限...</div>;
if (error) return <div>错误: {error}</div>;
if (!hasAccess) return <div>访问被拒绝</div>;
return (
<div>
<h1>受保护的内容</h1>
<p>欢迎, {user?.name}</p>
</div>
);
}🛡️ 认证与守卫系统完全指南
认证 vs 守卫的区别
| 特性 | Auth (认证) | Guard (守卫) | |------|-------------|--------------| | 主要用途 | 身份验证 | 权限控制 | | 检查内容 | 用户是否已登录 | 用户是否有权限访问 | | 失败后果 | 重定向到登录页 | 重定向到错误页 / 拒绝页 | | 适用场景 | 保护需要登录的页面 | 保护需要特定权限的页面 | | 执行顺序 | 优先执行 | 在认证通过后执行 |
🔐 认证系统详解
1. 基础认证 - defineAuth
最简单的认证方式,适合基础的登录检查
import { defineAuth } from '@gulibs/vgrove-client';
// 页面级认证文件:pages/dashboard/auth.ts
export default defineAuth({
name: 'dashboard-auth', // 认证名称(可选)
redirectTo: '/login', // 失败时重定向路径
errorMessage: '请先登录访问仪表板', // 错误消息
check: (context) => { // 认证检查函数
// context 包含:path, params, query, user, roles, permissions, data
const token = localStorage.getItem('auth_token');
// ⚠️ 关键:必须返回 boolean 值
return !!token;
}
});❌ 常见错误:
// 错误1:没有返回值
check: (context) => {
const token = localStorage.getItem('auth_token');
// ❌ 缺少 return 语句
}
// 错误2:返回非布尔值
check: (context) => {
return localStorage.getItem('auth_token'); // ❌ 返回 string | null
}
// 错误3:异步函数但没有正确处理
check: (context) => {
return fetch('/api/verify').then(res => res.ok); // ❌ 返回 Promise
}✅ 正确用法:
// 同步检查
check: (context) => {
const token = localStorage.getItem('auth_token');
return !!token; // ✅ 返回 boolean
}
// 异步检查
check: async (context) => {
try {
const response = await fetch('/api/verify', {
headers: { Authorization: `Bearer ${context.user?.token}` }
});
return response.ok; // ✅ 返回 boolean
} catch (error) {
console.error('Auth check failed:', error);
return false; // ✅ 异常时返回 false
}
}2. 智能认证 - defineSmartAuth ⭐
推荐使用!自动处理循环重定向,防止常见错误
import { defineSmartAuth } from '@gulibs/vgrove-client';
// 页面级认证文件:pages/auth.ts (根目录)
export default defineSmartAuth({
name: 'root-smart-auth',
redirectTo: '/login',
errorMessage: '请先登录后访问应用',
// 🚀 智能特性配置
enableLoopDetection: true, // 启用循环重定向检测
excludeCommonPublicPaths: true, // 自动排除常见公共路径
publicPaths: [ // 自定义公共路径
'/about',
'/help',
'/docs/*', // 支持通配符
'/api/*'
],
check: (context) => {
// 智能认证会自动处理:
// 1. 如果是公共路径,直接允许访问
// 2. 如果检测到循环重定向,自动阻止
// 3. 然后才执行这里的检查逻辑
const token = localStorage.getItem('auth_token');
console.log('🔐 Smart Auth Check:', {
path: context.path,
hasToken: !!token,
user: context.user
});
return !!token;
}
});内置公共路径列表:
// defineSmartAuth 自动排除这些路径:
const DEFAULT_PUBLIC_PATHS = [
'/login', '/signin', '/sign-in',
'/register', '/signup', '/sign-up',
'/auth/login', '/auth/signin',
'/auth/register', '/auth/signup',
'/reset-password', '/forgot-password',
'/verify-email', '/verify',
'/public', '/'
];3. 根目录认证 - defineRootAuth
专用于整个应用的登录保护
import { defineRootAuth } from '@gulibs/vgrove-client';
// 页面级认证文件:pages/auth.ts
export default defineRootAuth({
redirectTo: '/login',
errorMessage: '请先登录后访问应用',
publicPaths: ['/help', '/about'], // 自定义公共路径
check: (context) => {
const token = localStorage.getItem('auth_token');
return !!token;
}
});🛡️ 守卫系统详解
1. 自定义守卫 - defineGuard
适用于复杂的业务逻辑判断
import { defineGuard } from '@gulibs/vgrove-client';
// 页面级守卫文件:pages/admin/guard.ts
export default defineGuard({
name: 'admin-access', // 守卫名称
type: 'custom', // 守卫类型
redirectTo: '/forbidden', // 失败时重定向
errorMessage: '需要管理员权限访问此页面', // 错误消息
condition: (context) => { // 守卫条件函数
// context 包含:path, params, query, user, permissions, roles, data
// 检查用户角色
if (!context.user?.role) return false;
// 检查是否是管理员
const isAdmin = context.user.role === 'admin';
// 检查时间限制(示例:只允许工作时间访问)
const now = new Date();
const workingHours = now.getHours() >= 9 && now.getHours() <= 18;
return isAdmin && workingHours;
}
});2. 角色守卫 - defineRoleGuard
基于用户角色的访问控制
import { defineRoleGuard } from '@gulibs/vgrove-client';
// 在 _configs.tsx 中使用
export default defineConfigs({
guard: defineRoleGuard(
['admin', 'manager'], // 允许的角色列表
'/forbidden', // 可选:重定向路径
{ // 可选:额外选项
publicPaths: ['/help'],
enableLoopDetection: true
}
)
});3. 权限守卫 - definePermissionGuard
基于具体权限的访问控制
import { definePermissionGuard } from '@gulibs/vgrove-client';
// 在 _configs.tsx 中使用
export default defineConfigs({
guard: definePermissionGuard(
['user.write', 'user.update'], // 所需权限列表
'/no-permission', // 可选:重定向路径
{ // 可选:额外选项
enableLoopDetection: true
}
)
});4. 守卫工厂函数
快速创建常用的守卫
import { defineCustomGuard } from '@gulibs/vgrove-client';
// VIP 用户守卫
export const vipGuard = defineCustomGuard(
'vip-only',
(context) => context.user?.membership === 'VIP',
'/upgrade'
);
// 时间限制守卫
export const workingHoursGuard = defineCustomGuard(
'working-hours',
(context) => {
const hour = new Date().getHours();
return hour >= 9 && hour <= 18;
},
'/not-available'
);
// 地理位置守卫
export const regionGuard = defineCustomGuard(
'region-check',
async (context) => {
const region = await getUserRegion();
return ['US', 'CA', 'EU'].includes(region);
},
'/region-blocked'
);📁 使用方式对比
方式 1:单文件方式
src/pages/
├── dashboard/
│ ├── auth.ts # 认证配置
│ ├── guard.ts # 守卫配置
│ └── page.tsx # 页面组件
└── admin/
├── auth.ts
├── guard.ts
└── page.tsx优点: 功能分离清晰,易于理解 缺点: 文件较多,配置分散
方式 2:配置文件方式 (推荐)
src/pages/
├── dashboard/
│ ├── _configs.tsx # 集中配置
│ └── page.tsx # 页面组件
└── admin/
├── _configs.tsx
└── page.tsx优点: 配置集中,支持自定义组件,功能完整 缺点: 单文件可能较大
完整示例:
// pages/dashboard/_configs.tsx
import { defineConfigs, defineSmartAuth, defineGuard } from '@gulibs/vgrove-client';
import React from 'react';
// 自定义错误组件
const AccessDeniedError = ({ error, retry }: { error?: Error; retry?: () => void }) => (
<div className="access-denied">
<h2>访问被拒绝</h2>
<p>{error?.message}</p>
<button onClick={retry}>重试</button>
<button onClick={() => window.location.href = '/login'}>
重新登录
</button>
</div>
);
// 自定义加载组件
const AuthLoading = ({ progress }: { progress?: number }) => (
<div className="auth-loading">
<div className="spinner" />
<p>验证身份中...</p>
{progress && <div>进度: {Math.round(progress * 100)}%</div>}
</div>
);
export default defineConfigs({
// 智能认证配置
auth: defineSmartAuth({
name: 'dashboard-smart-auth',
redirectTo: '/login',
errorMessage: '请先登录访问仪表板',
enableLoopDetection: true,
excludeCommonPublicPaths: true,
check: (context) => {
const token = localStorage.getItem('auth_token');
console.log('Dashboard auth check:', {
path: context.path,
hasToken: !!token
});
return !!token;
}
}),
// 守卫配置
guard: defineGuard({
name: 'dashboard-access',
type: 'custom',
redirectTo: '/upgrade',
errorMessage: '需要升级账户以访问仪表板',
condition: (context) => {
// 检查用户类型
if (!context.user) return false;
// 免费用户不能访问仪表板
return context.user.plan !== 'free';
}
}),
// 自定义错误组件
error: AccessDeniedError,
// 自定义加载组件
loading: AuthLoading
});🚨 常见错误和解决方案
错误 1:循环重定向死循环
症状: 页面不断重定向,浏览器显示"重定向次数过多"
原因: 登录页面设置了认证保护
// ❌ 错误示例
// pages/login/auth.ts
export default defineAuth({
redirectTo: '/login', // 重定向到自己
check: () => false // 总是返回false
});解决方案:
// ✅ 方案1:删除登录页面的认证文件
// 直接删除 pages/login/auth.ts
// ✅ 方案2:使用智能认证自动处理
export default defineSmartAuth({
redirectTo: '/login',
enableLoopDetection: true, // 自动检测并阻止循环
excludeCommonPublicPaths: true, // 自动排除登录页面
check: (context) => !!context.user
});错误 2:守卫执行顺序混乱
症状: 权限检查在认证检查之前执行
// ❌ 错误:在需要认证的页面只设置守卫
export default defineConfigs({
// 缺少 auth 配置
guard: defineGuard({
condition: (context) => context.user?.role === 'admin' // 用户可能为 null
})
});
// ✅ 正确:先认证,再守卫
export default defineConfigs({
// 先进行身份认证
auth: defineSmartAuth({
redirectTo: '/login',
check: (context) => !!context.user
}),
// 认证通过后检查权限
guard: defineGuard({
condition: (context) => {
// 此时 context.user 已经确保存在
return context.user?.role === 'admin';
},
redirectTo: '/forbidden'
})
});🎯 最佳实践
1. 分层认证架构
src/pages/
├── auth.ts # 根目录:基础身份认证
├── public/ # 公开页面(无需认证)
│ ├── login/
│ ├── register/
│ └── about/
├── user/ # 用户区域
│ ├── _configs.tsx # 用户认证 + 基础权限
│ ├── dashboard/
│ └── profile/
├── admin/ # 管理员区域
│ ├── _configs.tsx # 管理员认证 + 管理权限
│ ├── users/
│ └── settings/
└── premium/ # 高级功能区域
├── _configs.tsx # 会员认证 + 会员权限
└── analytics/2. 认证状态管理
// utils/auth.ts - 集中的认证工具
export class AuthManager {
private static instance: AuthManager;
static getInstance(): AuthManager {
if (!AuthManager.instance) {
AuthManager.instance = new AuthManager();
}
return AuthManager.instance;
}
// 获取当前用户
getCurrentUser(): User | null {
try {
const userData = localStorage.getItem('user_data');
return userData ? JSON.parse(userData) : null;
} catch {
return null;
}
}
// 检查认证状态
isAuthenticated(): boolean {
const token = localStorage.getItem('auth_token');
const user = this.getCurrentUser();
return !!(token && user && user.status === 'active');
}
// 检查角色
hasRole(roles: string[]): boolean {
const user = this.getCurrentUser();
return user?.roles?.some(role => roles.includes(role)) || false;
}
// 检查权限
hasPermission(permissions: string[]): boolean {
const user = this.getCurrentUser();
return permissions.every(permission =>
user?.permissions?.includes(permission)
);
}
// 登出
logout(): void {
localStorage.removeItem('auth_token');
localStorage.removeItem('user_data');
localStorage.removeItem('refresh_token');
// 触发自定义事件通知其他组件
window.dispatchEvent(new CustomEvent('authStateChange'));
// 重定向到登录页
window.location.href = '/public/login';
}
}
// 在认证配置中使用
export default defineSmartAuth({
check: (context) => {
const authManager = AuthManager.getInstance();
return authManager.isAuthenticated();
}
});3. 调试和开发工具
// utils/authDebug.ts - 认证调试工具
export class AuthDebugger {
private static enabled = process.env.NODE_ENV === 'development';
static log(message: string, data?: any): void {
if (!this.enabled) return;
console.group(`🔐 Auth Debug: ${message}`);
if (data) {
console.log('Data:', data);
}
console.log('Timestamp:', new Date().toISOString());
console.groupEnd();
}
static logAuthCheck(
checkName: string,
result: boolean,
context: any,
reason?: string
): void {
if (!this.enabled) return;
const emoji = result ? '✅' : '❌';
console.group(`${emoji} Auth Check: ${checkName}`);
console.log('Result:', result);
console.log('Context:', {
path: context.path,
user: context.user ? 'Present' : 'Missing',
roles: context.roles,
permissions: context.permissions
});
if (reason) console.log('Reason:', reason);
console.groupEnd();
}
}
// 在认证配置中使用
export default defineSmartAuth({
name: 'debug-auth',
redirectTo: '/login',
check: (context) => {
const token = localStorage.getItem('auth_token');
const result = !!token;
AuthDebugger.logAuthCheck(
'Token Check',
result,
context,
result ? 'Token found' : 'Token missing'
);
return result;
}
});🔧 调试指南
浏览器控制台调试命令
// 1. 检查当前认证状态
console.log('Auth Token:', localStorage.getItem('auth_token'));
console.log('User Data:', JSON.parse(localStorage.getItem('user_data') || 'null'));
// 2. 模拟登录状态
localStorage.setItem('auth_token', 'debug-token-123');
localStorage.setItem('user_data', JSON.stringify({
id: 'debug-user',
name: 'Debug User',
role: 'admin',
status: 'active',
permissions: ['read', 'write', 'admin']
}));
// 3. 清除认证状态
localStorage.removeItem('auth_token');
localStorage.removeItem('user_data');
localStorage.removeItem('refresh_token');
// 4. 检查页面保护状态
console.log('Current Path:', window.location.pathname);
console.log('Should be protected:', !window.location.pathname.includes('/public/'));📝 快速参考
认证类型对比表
| 场景 | 推荐方案 | 配置示例 |
|------|----------|----------|
| 简单登录检查 | defineAuth | check: (ctx) => !!ctx.user |
| 复杂认证逻辑 | defineSmartAuth | enableLoopDetection: true |
| 根目录保护 | defineRootAuth | publicPaths: ['/public/*'] |
| 角色权限 | defineRoleGuard | ['admin', 'manager'] |
| 具体权限 | definePermissionGuard | ['user.read', 'user.write'] |
| 自定义业务逻辑 | defineGuard | condition: (ctx) => {...} |
记住这些关键点
- ✅ 认证函数必须返回
boolean - ✅ 登录页面不要设置认证保护
- ✅ 使用
defineSmartAuth避免循环重定向 - ✅ 先设置
auth再设置guard - ✅ 异步认证要用
async/await - ✅ 错误处理要提供用户友好的提示
💡 提示: 对于初学者,建议先使用
defineSmartAuth和defineRoleGuard,它们内置了大部分常见错误的处理逻辑。
7. 高级国际化 Hooks
import {
useI18nKeyValidator,
useI18nNamespace,
usePlural,
useTranslateLocalized
} from '@gulibs/vgrove-client';
// 键验证 Hook
function I18nKeyValidator() {
const { hasKey, validateKey, getAvailableKeys } = useI18nKeyValidator();
const checkKey = (key: string) => {
if (hasKey(key)) {
console.log('键存在:', key);
} else {
const { suggestions } = validateKey(key);
console.log('键不存在,建议:', suggestions);
}
};
return (
<div>
<button onClick={() => checkKey('welcome.title')}>
检查键: welcome.title
</button>
<p>可用键数量: {getAvailableKeys().length}</p>
</div>
);
}
// 命名空间 Hook
function NamespaceExample() {
const { t, resources, exists } = useI18nNamespace('user');
if (!exists) return <div>命名空间不存在</div>;
return (
<div>
<h1>{t('profile.title')}</h1> {/* 相当于 user.profile.title */}
<p>{t('profile.description')}</p>
</div>
);
}
// 复数形式 Hook
function PluralExample() {
const plural = usePlural();
const [count, setCount] = useState(1);
return (
<div>
<p>{plural('message.count', count, { count })}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
// 本地化对象翻译 Hook
function LocalizedTranslation() {
const translateLocalized = useTranslateLocalized();
const localizedTitle = { localizedId: 'page.title' };
return (
<div>
<h1>{translateLocalized(localizedTitle)}</h1>
</div>
);
}8. I18nMessage 组件
import { I18nMessage, I18nTitle, I18nText, I18nSwitch } from '@gulibs/vgrove-client';
// 基础用法
function MyComponent() {
return (
<div>
<I18nMessage id="welcome.title" defaultValue="欢迎" />
<I18nMessage
id="welcome.description"
params={{ name: '用户' }}
defaultValue="你好,{name}!"
/>
</div>
);
}
// 使用便捷组件
function PageContent() {
return (
<div>
<I18nTitle id="page.title" level={1} />
<I18nText id="page.subtitle" className="text-gray-600" />
{/* 支持不同格式 */}
<I18nMessage
id="rich.content"
format="markdown"
defaultValue="**粗体** 和 *斜体* 文本"
/>
{/* 组件标记格式 */}
<I18nMessage
id="component.text"
format="component"
defaultValue="点击 {strong}这里{/strong} 查看更多"
/>
</div>
);
}
// 语言切换器
function LanguageSwitcher() {
return (
<I18nSwitch
variant="select"
locales={[
{ code: 'zh', label: '中文' },
{ code: 'en', label: 'English' }
]}
onLocaleChange={(locale) => console.log('语言切换到:', locale)}
/>
);
}9. 性能优化工具详细示例
import {
PerformanceCache,
BatchProcessor,
PerformanceTracker,
MemoryOptimizer,
Debouncer,
measureAsync
} from '@gulibs/vgrove-client';
// 缓存管理
function CacheExample() {
const cache = useMemo(() => new PerformanceCache<string, any>(100), []);
const fetchWithCache = useCallback(async (key: string) => {
if (cache.has(key)) {
return cache.get(key);
}
const data = await fetch(`/api/data/${key}`).then(res => res.json());
cache.set(key, data);
return data;
}, [cache]);
return (
<div>
<button onClick={() => fetchWithCache('user-1')}>
获取用户数据(带缓存)
</button>
<p>缓存大小: {cache.size()}</p>
</div>
);
}
// 批处理示例
function BatchProcessingExample() {
const batchProcessor = useMemo(() => new BatchProcessor(5, 100), []);
const processBatchData = useCallback(async () => {
const userIds = ['1', '2', '3', '4', '5', '6', '7', '8'];
const users = await batchProcessor.processBatch(
userIds,
async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
);
console.log('批处理完成:', users);
}, [batchProcessor]);
return (
<button onClick={processBatchData}>
批量处理用户数据
</button>
);
}
// 性能追踪示例
function PerformanceExample() {
const tracker = useMemo(() => new PerformanceTracker(), []);
const expensiveOperation = useCallback(async () => {
tracker.startTimer('expensive-operation');
// 模拟耗时操作
await new Promise(resolve => setTimeout(resolve, 1000));
const duration = tracker.endTimer('expensive-operation');
console.log(`操作耗时: ${duration}ms`);
tracker.increment('operation-count');
console.log(`操作次数: ${tracker.getCounter('operation-count')}`);
}, [tracker]);
return (
<div>
<button onClick={expensiveOperation}>
执行耗时操作
</button>
<button onClick={() => console.log(tracker.getStats())}>
查看性能统计
</button>
</div>
);
}
// 防抖示例
function DebounceExample() {
const debouncer = useMemo(() => new Debouncer(), []);
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = useCallback((term: string) => {
debouncer.debounce('search', () => {
console.log('搜索:', term);
// 执行实际搜索
}, 500);
}, [debouncer]);
return (
<input
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
handleSearch(e.target.value);
}}
placeholder="搜索(防抖 500ms)"
/>
);
}
// 内存优化示例
function MemoryOptimizationExample() {
useEffect(() => {
const memoryOptimizer = MemoryOptimizer.getInstance();
// 添加清理任务
memoryOptimizer.addCleanupTask(() => {
console.log('清理缓存数据');
// 清理各种缓存
});
// 启动定期清理(5分钟)
memoryOptimizer.startPeriodicCleanup(5 * 60 * 1000);
return () => {
// 组件卸载时进行清理
memoryOptimizer.runCleanup();
};
}, []);
return <div>内存优化已启用</div>;
}
// 异步性能测量
function AsyncMeasurementExample() {
const measureApiCall = useCallback(async () => {
const result = await measureAsync('api-call', async () => {
const response = await fetch('/api/data');
return response.json();
});
console.log('API 调用结果:', result);
console.log('耗时:', result.duration, 'ms');
}, []);
return (
<button onClick={measureApiCall}>
测量 API 调用性能
</button>
);
}10. 工具函数详细示例
import {
normalizePath,
parseQuery,
buildQuery,
updateQuery,
debounce,
throttle,
deepMerge,
deepClone,
unique,
groupBy,
isEmpty,
safeParseInt,
toCamelCase,
toKebabCase,
capitalize,
truncate,
safely,
safelyAsync
} from '@gulibs/vgrove-client';
function UtilityFunctionsExample() {
// 路径处理
const normalizedPath = normalizePath('//api//users//profile//');
console.log(normalizedPath); // '/api/users/profile'
// 查询参数处理
const queryParams = parseQuery('?name=john&age=25&active=true');
console.log(queryParams); // { name: 'john', age: '25', active: 'true' }
const queryString = buildQuery({ name: 'jane', age: 30, active: false });
console.log(queryString); // 'name=jane&age=30&active=false'
const updatedUrl = updateQuery('/users', { page: 2, size: 10 });
console.log(updatedUrl); // '/users?page=2&size=10'
// 防抖和节流
const debouncedSearch = debounce((term: string) => {
console.log('搜索:', term);
}, 500);
const throttledScroll = throttle(() => {
console.log('滚动事件');
}, 100);
// 对象操作
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
const merged = deepMerge({ x: 1 }, { y: 2, z: { a: 3 } });
// 数组操作
const items = [1, 2, 2, 3, 4, 4, 5];
const uniqueItems = unique(items);
console.log(uniqueItems); // [1, 2, 3, 4, 5]
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' }
];
const groupedUsers = groupBy(users, user => user.role);
console.log(groupedUsers);
// { admin: [Alice, Charlie], user: [Bob] }
// 字符串操作
const camelCase = toCamelCase('hello-world-example');
console.log(camelCase); // 'helloWorldExample'
const kebabCase = toKebabCase('HelloWorldExample');
console.log(kebabCase); // 'hello-world-example'
const capitalized = capitalize('hello world');
console.log(capitalized); // 'Hello world'
const truncated = truncate('This is a very long text', 10);
console.log(truncated); // 'This is a...'
// 安全操作
const safeValue = safely(() => JSON.parse('invalid json'), null);
console.log(safeValue); // null
const safeAsyncValue = safelyAsync(
async () => {
const response = await fetch('/api/data');
return response.json();
},
{ error: 'Failed to load' }
);
// 类型检查和转换
const emptyCheck = isEmpty(''); // true
const numberValue = safeParseInt('123abc', 0); // 123
return (
<div>
<h3>工具函数示例</h3>
<p>标准化路径: {normalizedPath}</p>
<p>解析查询: {JSON.stringify(queryParams)}</p>
<p>构建查询: {queryString}</p>
<p>唯一数组: {JSON.stringify(uniqueItems)}</p>
<input
onChange={(e) => debouncedSearch(e.target.value)}
placeholder="防抖搜索"
/>
<div
onScroll={throttledScroll}
style={{ height: '100px', overflow: 'scroll' }}
>
滚动内容...
</div>
</div>
);
}11. 国际化路由集成
import {
createI18nClient,
createI18nRouteLoader,
createI18nHandle,
useRouteI18n
} from '@gulibs/vgrove-client';
// 创建国际化客户端
const i18nClient = createI18nClient({
defaultLocale: 'zh',
supportedLocales: ['zh', 'en'],
basePath: '/api/locales'
});
// 创建路由加载器
const i18nLoader = createI18nRouteLoader(i18nClient, {
preloadAll: false,
delay: 0
});
// 路由配置
export const route = {
path: '/products/:id',
loader: i18nLoader,
handle: createI18nHandle({
meta: {
title: 'products.detail.title',
description: 'products.detail.description'
},
pageExtras: (context) => {
const { params, query } = context;
return (
<div>
<ProductBreadcrumb productId={params.id} />
{query.variant && <VariantSelector variant={query.variant} />}
</div>
);
}
})
};
// 组件使用
function ProductDetailPage() {
const {
locale,
isLoaded,
getLocalizedUrl,
waitForI18n
} = useRouteI18n();
const { t } = useI18n();
const { meta, pageExtras } = useHandle();
// 等待国际化资源加载
useEffect(() => {
waitForI18n().then(() => {
console.log('国际化资源加载完成');
});
}, [waitForI18n]);
if (!isLoaded) {
return <div>Loading translations...</div>;
}
return (
<div>
<h1>{t('products.detail.title')}</h1>
{/* 动态生成的页面额外内容 */}
{pageExtras}
{/* 语言切换按钮 */}
<button onClick={() => window.location.href = getLocalizedUrl('en')}>
English
</button>
</div>
);
}12. 页面配置和 Hooks
import {
useHandle,
defineHandle,
usePageMeta,
useBreadcrumbs,
useDocumentTitle
} from '@gulibs/vgrove-client';
// 定义页面处理配置
export const handle = defineHandle({
meta: {
title: 'page.title',
description: '页面描述'
},
breadcrumbs: {
href: '/current-page',
children: '当前页面'
},
// pageExtras 可以是静态内容或函数
pageExtras: (context) => {
// 可以访问路由上下文信息
const { path, params, query, matches, handle } = context;
return (
<div>
<p>当前路径: {path}</p>
<p>路由参数: {JSON.stringify(params)}</p>
<p>查询参数: {JSON.stringify(query)}</p>
<p>匹配的路由数量: {matches.length}</p>
{params.id && <p>商品ID: {params.id}</p>}
{query.tab && <p>当前标签: {query.tab}</p>}
</div>
);
}
});
function MyPage() {
// 获取页面配置
const { meta, breadcrumbs, pageExtras } = useHandle();
return (
<div>
<h1>{meta?.title}</h1>
{/* 渲染动态生成的额外内容 */}
{pageExtras}
{/* 页面内容 */}
</div>
);
}
// 使用页面元数据 Hook
function PageWithMeta() {
const meta = usePageMeta(handle, { enableI18n: true });
const updateTitle = useDocumentTitle(handle, 'My App', { enableI18n: true });
useEffect(() => {
updateTitle(); // 设置文档标题
}, [updateTitle]);
return (
<div>
<h1>{meta?.title}</h1>
<p>{meta?.description}</p>
</div>
);
}
// 使用面包屑 Hook
function PageWithBreadcrumbs() {
const breadcrumbs = useBreadcrumbs(handle, {
enableI18n: true,
includeMatches: true
});
return (
<div>
<nav>
{breadcrumbs.map((breadcrumb, index) => (
<span key={index}>
{index > 0 && ' > '}
<a href={breadcrumb.href}>{breadcrumb.children}</a>
</span>
))}
</nav>
{/* 页面内容 */}
</div>
);
}13. 综合应用示例
import {
RouteProtectionWrapper,
I18nProvider,
useUser,
useI18n,
I18nMessage,
createI18nClient,
defineAuth,
defineGuard,
PerformanceCache,
useStorage
} from '@gulibs/vgrove-client';
// 创建国际化客户端
const i18nClient = createI18nClient({
defaultLocale: 'zh',
supportedLocales: ['zh', 'en'],
persistence: { enabled: true },
detectBrowserLanguage: true
});
// 创建缓存
const appCache = new PerformanceCache<string, any>(200);
// 定义认证守卫
const authGuard = defineAuth({
redirectTo: '/login',
errorMessage: 'auth.required'
});
// 定义VIP守卫
const vipGuard = defineGuard({
name: 'vip-only',
condition: (context) => context.user?.membership === 'VIP',
redirectTo: '/upgrade',
errorMessage: 'vip.required'
});
// 主应用组件
function App() {
return (
<I18nProvider client={i18nClient}>
<AppContent />
</I18nProvider>
);
}
function AppContent() {
const { isAuthenticated, user } = useUser({
storagePrefix: 'myapp',
autoRefreshInterval: 15 * 60 * 1000,
loginApi: async (credentials) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
return response.json();
}
});
const { locale, setLocale } = useI18n();
const [userPreferences, setUserPreferences] = useStorage('user_prefs', {
theme: 'light',
notifications: true
});
return (
<div>
{/* 顶部导航 */}
<header>
<I18nMessage id="app.title" defaultValue="我的应用" />
{isAuthenticated && (
<span>
<I18nMessage
id="welcome.user"
params={{ name: user?.name }}
defaultValue="欢迎, {name}!"
/>
</span>
)}
<button onClick={() => setLocale(locale === 'zh' ? 'en' : 'zh')}>
{locale === 'zh' ? 'English' : '中文'}
</button>
</header>
{/* 主内容区 */}
<main>
{/* 公开页面 */}
<PublicPage />
{/* 需要认证的页面 */}
<RouteProtectionWrapper
guards={[authGuard]}
loadingElement={
<I18nMessage id="loading.auth" defaultValue="验证中..." />
}
component={<ProtectedPage />}
/>
{/* 需要VIP权限的页面 */}
<RouteProtectionWrapper
guards={[authGuard, vipGuard]}
loadingElement={
<I18nMessage id="loading.vip" defaultValue="检查VIP权限..." />
}
component={<VIPPage />}
/>
</main>
{/* 用户偏好设置 */}
<aside>
<h3>
<I18nMessage id="settings.title" defaultValue="设置" />
</h3>
<label>
<input
type="checkbox"
checked={userPreferences.notifications}
onChange={(e) =>
setUserPreferences({
...userPreferences,
notifications: e.target.checked
})
}
/>
<I18nMessage id="settings.notifications" defaultValue="启用通知" />
</label>
</aside>
</div>
);
}
// 公开页面
function PublicPage() {
return (
<div>
<I18nMessage id="public.welcome" defaultValue="欢迎访问我们的网站" />
</div>
);
}
// 受保护页面
function ProtectedPage() {
const { user } = useUser();
return (
<div>
<I18nMessage
id="protected.welcome"
params={{ name: user?.name }}
defaultValue="欢迎进入会员区域, {name}!"
/>
</div>
);
}
// VIP页面
function VIPPage() {
const { user } = useUser();
return (
<div>
<I18nMessage
id="vip.welcome"
params={{ name: user?.name }}
defaultValue="欢迎尊贵的VIP用户, {name}!"
/>
</div>
);
}🎯 高级用法
中间件系统
import { defineMiddleware } from '@gulibs/vgrove-client';
const loggingMiddleware = defineMiddleware({
name: 'logging',
priority: 10,
handler: async (context, next) => {
console.log(`访问页面: ${context.path}`);
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(`页面处理耗时: ${duration}ms`);
}
});
// 全局性能监控中间件
const performanceMiddleware = defineMiddleware({
name: 'performance',
priority: 1,
handler: async (context, next) => {
if (window.performance) {
const mark = `route-${context.path}-start`;
performance.mark(mark);
}
await next();
if (window.performance) {
const endMark = `route-${context.path}-end`;
performance.mark(endMark);
performance.measure(`route-${context.path}`, `route-${context.path}-start`, endMark);
}
}
});
<RouteProtectionWrapper
middlewares={[performanceMiddleware, loggingMiddleware]}
component={<YourComponent />}
/>国际化资源加载器
import { createI18nLoader, ViteI18nLoader, createI18nClient } from '@gulibs/vgrove-client';
// 1. 基础资源加载器
const loader = createI18nLoader({
basePath: '/api/locales',
extensions: ['.json'],
cache: true,
debug: true
});
// 2. 自定义获取函数
const customLoader = createI18nLoader({
fetchResources: async (locale, path) => {
const response = await fetch(`/api/i18n/${locale}`);
if (!response.ok) {
throw new Error(`Failed to load locale: ${response.statusText}`);
}
return response.json();
},
onLoad: (locale, data) => {
console.log(`✅ Loaded ${locale}:`, Object.keys(data));
},
onError: (locale, error) => {
console.error(`❌ Failed to load ${locale}:`, error);
}
});
// 3. 高级加载器实例
const advancedLoader = new ViteI18nLoader({
basePath: '/locales',
cache: true,
cacheTime: 30 * 60 * 1000, // 30分钟
debug: process.env.NODE_ENV === 'development'
});
// 4. 与国际化客户端集成
const i18nClient = createI18nClient({
defaultLocale: 'zh',
supportedLocales: ['zh', 'en'],
basePath: '/api/locales',
cache: true,
detectBrowserLanguage: true,
persistence: {
enabled: true,
key: 'user_locale',
storage: 'localStorage'
}
});
// 5. 手动预加载资源
await loader.preloadResources(['zh', 'en']);
// 6. 检查加载器状态
console.log('Available keys:', loader.getAvailableKeys?.());
console.log('Has key "welcome":', loader.hasKey?.('welcome'));
console.log('Using virtual module:', advancedLoader.isUsingVirtualModule?.());复杂路由守卫
// 角色守卫
const roleGuard = defineAuth({
roles: ['admin', 'moderator'],
redirectTo: '/login',
errorMessage: '需要以下角色之一: admin, moderator'
});
// 权限守卫
const permissionGuard = defineGuard({
name: 'permission-user:read,user:write',
condition: (context) => {
return context.user?.permissions?.includes('user:read') &&
context.user?.permissions?.includes('user:write');
},
redirectTo: '/forbidden',
errorMessage: '需要以下权限: user:read, user:write'
});
// 自定义复合守卫
const complexGuard = defineGuard({
name: 'complex-auth',
condition: async (context) => {
// 检查用户是否已认证
if (!context.user) return false;
// 检查时间限制
const now = new Date();
const workingHours = now.getHours() >= 9 && now.getHours() <= 18;
// 检查用户类型和时间
return context.user.type === 'employee' ? workingHours : true;
},
redirectTo: '/access-denied',
errorMessage: '当前时间段无法访问'
});
// 环境守卫
const environmentGuard = defineGuard({
name: 'environment',
condition: () => {
return process.env.NODE_ENV === 'development' ||
localStorage.getItem('feature_flag_enabled') === 'true';
},
redirectTo: '/not-available'
});性能优化工具
import {
PerformanceCache,
BatchProcessor,
PerformanceTracker,
MemoryOptimizer
} from '@gulibs/vgrove-client';
// 创建缓存实例
const cache = new PerformanceCache<string, any>(100);
// 批处理器
const processor = new BatchProcessor(5, 100);
// 性能追踪
const tracker = new PerformanceTracker();
// 内存优化器
const memoryOptimizer = MemoryOptimizer.getInstance();
function useOptimizedData() {
useEffect(() => {
// 启动性能追踪
tracker.startTimer('data-loading');
// 批处理数据加载
const loadData = async () => {
const items = ['user', 'settings', 'permissions'];
const results = await processor.processBatch(
items,
async (item) => {
const cached = cache.get(item);
if (cached) return cached;
const data = await fetchData(item);
cache.set(item, data);
return data;
}
);
return results;
};
loadData().finally(() => {
const duration = tracker.endTimer('data-loading');
console.log(`数据加载耗时: ${duration}ms`);
});
// 注册清理任务
memoryOptimizer.addCleanupTask(() => {
cache.clear();
});
return () => {
memoryOptimizer.runCleanup();
};
}, []);
}工具函数和实用程序
import {
normalizePath,
parseQuery,
buildQuery,
debounce,
throttle,
deepMerge,
safeParseInt,
isEmpty,
measureTime
} from '@gulibs/vgrove-client';
// 路径处理
const normalizedPath = normalizePath('//path//to//page//');
// => '/path/to/page'
// 查询参数处理
const params = parseQuery('?name=john&age=25&active=true');
// => { name: 'john', age: '25', active: 'true' }
const query = buildQuery({ name: 'jane', age: 30 });
// => 'name=jane&age=30'
// 防抖和节流
const debouncedSearch = debounce((query: string) => {
searchAPI(query);
}, 500);
const throttledScroll = throttle(() => {
updateScrollPosition();
}, 100);
// 性能测量
const timedFunction = measureTime(expensiveFunction, 'expensive-operation');
// 数据处理
const config = deepMerge(defaultConfig, userConfig);
const count = safeParseInt(userInput, 0);
const hasData = !isEmpty(responseData);📖 API 参考
路由保护
RouteProtectionWrapper
路由保护包装器组件。
Props:
interface RouteProtectionWrapperProps {
guards?: (AuthOptions | GuardOptions | Function)[];
middlewares?: (MiddlewareOptions | Function)[];
loadingElement?: React.ReactNode;
component: React.ReactNode;
children?: React.ReactNode;
}defineAuth(options: AuthOptions)
定义认证守卫。
interface AuthOptions {
name?: string;
redirectTo?: string;
errorMessage?: string;
check?: (context: AuthContext) => boolean | Promise<boolean>;
roles?: string[];
permissions?: string[];
}defineGuard(options: GuardOptions)
定义自定义守卫。
interface GuardOptions {
name?: string;
type?: 'auth' | 'role' | 'permission' | 'custom';
redirectTo?: string;
errorMessage?: string;
condition: (context: GuardContext) => boolean | Promise<boolean>;
}defineMiddleware(options: MiddlewareOptions)
定义中间件。
interface MiddlewareOptions {
name?: string;
priority?: number;
devOnly?: boolean;
handler: (context: MiddlewareContext, next: () => Promise<void> | void) => Promise<void> | void;
}状态管理
useUser<TUser, TCredentials>(options: UseUserOptions)
用户状态管理 Hook。
返回值:
interface AuthState<TUser> & AuthActions<TUser, TCredentials> {
user: TUser | null;
token: string | null;
refreshToken: string | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
login: (credentials: TCredentials) => Promise<void>;
logout: () => void;
refreshUser: () => Promise<void>;
refreshTokenAction: () => Promise<void>;
updateUser: (userData: Partial<TUser>) => void;
setToken: (token: string) => void;
setRefreshToken: (refreshToken: string) => void;
clearError: () => void;
}useStorage<T>(key: string, defaultValue: T, options?)
基于 @gulibs/react-storage 的通用存储 Hook,支持过期时间。
interface UseStorageOptions {
/** 存储类型 */
storage?: 'local' | 'session';
/** 过期时间(毫秒),0 表示永不过期 */
maxAge?: number;
/** 是否自动清理过期数据 */
autoCleanup?: boolean;
/** 清理间隔(毫秒),默认 5 分钟 */
cleanupInterval?: number;
}
function useStorage<T>(
key: string,
defaultValue: T,
options?: UseStorageOptions
): [
T, // 当前值
(value: T) => void, // 设置值函数
() => void, // 移除值函数
(value: T, maxAge?: number) => void, // 设置值并指定过期时间
() => boolean, // 检查是否过期
() => void // 清理过期数据
]返回值:
[0]- 当前值[1]- 设置值函数(使用默认过期时间)[2]- 移除值函数[3]- 设置值并指定过期时间函数[4]- 检查是否过期函数[5]- 手动清理过期数据函数
示例:
// 基础使用
const [userPrefs, setUserPrefs, removeUserPrefs] = useStorage('user_prefs', {
theme: 'light'
}, { storage: 'local' });
// 带过期时间(1小时)
const [cache, setCache, removeCache, setCacheWithExpiry, isCacheExpired, cleanupCache] = useStorage(
'api_cache',
{},
{
storage: 'local',
maxAge: 60 * 60 * 1000, // 1小时过期
autoCleanup: true // 自动清理
}
);
// 使用指定过期时间设置数据
setCacheWithExpiry({ data: 'temp' }, 5 * 60 * 1000); // 5分钟后过期
// 检查数据是否过期
if (isCacheExpired()) {
console.log('数据已过期');
}
// 手动清理过期数据
cleanupCache();useUserState<TUser>(storagePrefix?, useSessionStorage?)
只读用户状态 Hook。
function useUserState<TUser = any>(
storagePrefix?: string,
useSessionStorage?: boolean
): {
user: TUser | null;
token: string | null;
refreshToken: string | null;
isAuthenticated: boolean;
}useAuthToken(storagePrefix?, useSessionStorage?)
Token 管理 Hook。
function useAuthToken(
storagePrefix?: string,
useSessionStorage?: boolean
): {
token: string | null;
refreshToken: string | null;
setToken: (token: string) => void;
setRefreshToken: (refreshToken: string) => void;
clearToken: () => void;
clearRefreshToken: () => void;
clearAllTokens: () => void;
hasToken: boolean;
hasRefreshToken: boolean;
}国际化
I18nProvider
国际化提供者组件。
interface I18nProviderProps {
children: ReactNode;
client?: I18nClient | I18nClientConfig;
defaultLocale?: string;
locales?: string[];
loadResources?: (locale: string) => Promise<Record<string, any>>;
resources?: Record<string, Record<string, any>>;
}I18nMessage
国际化消息组件,修复了刷新时找不到翻译内容的问题。
interface I18nMessageProps<Keys extends string = string> {
/** 翻译键 - 类型安全,与 @gulibs/vgrove-i18n 生成的类型兼容 */
id: LocalizedType<Keys>;
/** 插值参数 */
params?: Record<string, string | number | boolean>;
/** 默认值(当翻译不存在时使用) */
defaultValue?: string;
/** 渲染格式 */
format?: 'text' | 'html' | 'markdown' | 'component';
/** 自定义渲染函数 */
render?: (text: string) => ReactNode;
/** 包装元素 */
as?: keyof React.JSX.IntrinsicElements | React.ComponentType<Record<string, unknown>>;
/** 自定义类名 */
className?: string;
}I18nSwitch
语言切换组件。
interface I18nSwitchProps {
/** 自定义类名 */
className?: string;
/** 语言选项,如果不提供则使用可用语言列表 */
locales?: { code: string; label: string }[];
/** 是否显示语言标签 */
showLabels?: boolean;
/** 切换按钮样式 */
variant?: 'select' | 'button' | 'tabs';
/** 语言切换回调 */
onLocaleChange?: (locale: string) => void;
}便捷组件
// 国际化标题
function I18nTitle<Keys extends string = string>(
props: Omit<I18nMessageProps<Keys>, 'as' | 'format'> & { level?: 1 | 2 | 3 | 4 | 5 | 6 }
): JSX.Element
// 国际化段落
function I18nParagraph<Keys extends string = string>(
props: Omit<I18nMessageProps<Keys>, 'as' | 'format'>
): JSX.Element
// 国际化文本
function I18nText<Keys extends string = string>(
props: Omit<I18nMessageProps<Keys>, 'as' | 'format'>
): JSX.Element调试和优化 Hooks
// I18nMessage 调试 Hook
function useI18nMessageDebug<Keys extends string = string>(): {
isReady: boolean;
locale: string;
debugInfo: {
totalKeys: number;
isUsingVirtualModule: boolean;
hasKeys: boolean;
};
validateKey: (key: string) => {
exists: boolean;
ready: boolean;
locale: string;
};
}
// I18nMessage 性能优化 Hook
function useI18nMessageMemo<Keys extends string = string>(
id: LocalizedType<Keys>,
params?: Record<string, string | number | boolean>,
defaultValue?: string
): stringuseI18n()
国际化 Hook。
interface I18nContextType {
locale: string;
setLocale: (locale: string) => Promise<void>;
t: (key: string, params?: Record<string, any>) => string;
isReady: boolean;
availableLocales: string[];
availableKeys: string[];
hasKey: (key: string) => boolean;
getNamespaceResources: (namespace: string) => Record<string, any> | undefined;
isUsingVirtualModule: boolean;
}createI18nClient(config: I18nClientConfig)
创建国际化客户端。
interface I18nClientConfig extends I18nLoaderConfig {
defaultLocale: string;
supportedLocales?: string[];
resources?: Record<string, Record<string, any>>;
fallbackToDefault?: boolean;
detectBrowserLanguage?: boolean;
persistence?: {
enabled?: boolean;
key?: string;
storage?: 'localStorage' | 'sessionStorage';
};
interpolation?: {
prefix?: string;
suffix?: string;
escape?: (value: any) => string;
};
}createI18nLoader(config: I18nLoaderConfig)
创建资源加载器。
interface I18nLoaderConfig {
basePath?: string;
extensions?: string[];
localePattern?: 'directory' | 'filename';
defaultLocale?: string;
cacheTime?: number;
cache?: boolean;
debug?: boolean;
onLoad?: (locale: string, data: Record<string, any>) => void;
onError?: (locale: string, error: Error) => void;
fetchResources?: (locale: string, path: string) => Promise<Record<string, any>>;
}页面配置
usePageConfig<Keys>(handle?, options?)
获取页面配置。
useI18nPageConfig<Keys>(handle?)
获取国际化页面配置。
useHandle<Keys>()
获取当前页面的 handle。
PageExtrasContext 接口
pageExtras 函数的上下文参数接口。
interface PageExtrasContext<Keys extends string = string> {
/** 当前路由路径 */
path: string;
/** 路由参数 */
params: Record<string, string>;
/** 查询参数 */
query: Record<string, string>;
/** 当前路由匹配信息 */
matches: UIMatch[];
/** 当前页面的 handle */
handle: PageHandle<Keys>;
}使用示例:
export const handle = defineHandle({
meta: {
title: 'products.title'
},
pageExtras: (context: PageExtrasContext) => {
const { path, params, query, matches, handle } = context;
// 根据路由参数动态生成内容
if (params.productId) {
return <ProductBanner productId={params.productId} />;
}
// 根据查询参数显示不同内容
if (query.mode === 'debug') {
return (
<DebugInfo
path={path}
matches={matches.length}
handleMeta={handle.meta}
/>
);
}
return null;
}
});useBreadcrumbs<Keys>(handle?, options?)
获取面包屑配置。
useDocumentTitle<Keys>(handle?, suffix?, options?)
设置文档标题。
国际化路由集成
createI18nRouteLoader(i18nClient, options?)
创建国际化路由加载器,用于在路由加载时预加载所需的翻译资源。
function createI18nRouteLoader(
i18nClient: I18nClient,
options?: I18nRouteLoaderOptions
): (args: LoaderFunctionArgs) => Promise<RouteI18nData>
interface I18nRouteLoaderOptions {
/** 要预加载的语言列表,默认使用当前语言 */
locales?: string[];
/** 是否预加载所有支持的语言 */
preloadAll?: boolean;
/** 延迟加载时间(毫秒),用于演示加载状态 */
delay?: number;
}useRouteI18n()
在组件中使用路由国际化数据的 Hook。
function useRouteI18n(): {
/** 当前语言 */
locale: string;
/** 国际化资源是否已加载 */
isLoaded: boolean;
/** 获取当前路径的语言参数 */
getLocaleFromPath: () => string | null;
/** 切换语言的 URL 生成器 */
getLocalizedUrl: (newLocale: string) => string;
/** 等待国际化资源加载完成 */
waitForI18n: () => Promise<void>;
}createI18nHandle<Keys>(baseHandle?)
创建支持国际化的页面处理配置。
function createI18nHandle<Keys extends string = string>(
baseHandle?: Partial<PageHandle<Keys>>
): I18nHandle<Keys>
interface I18nHandle<Keys extends string = string> extends PageHandle<Keys> {
/** 标记为需要国际化处理 */
i18n: boolean;
/** 国际化元数据处理器 */
getI18nMeta?: (t: (key: string) => string) => {
title?: string;
description?: string;
[key: string]: any;
};
}enhanceRoutesWithI18n(routes, i18nClient, options?)
为现有路由配置添加国际化支持。
function enhanceRoutesWithI18n(
routes: any[],
i18nClient: I18nClient,
options?: EnhanceRoutesI18nOptions
): any[]
interface EnhanceRoutesI18nOptions {
/** 是否为所有路由添加国际化 loader */
autoAddLoader?: boolean;
}性能优化
PerformanceCache<K, V>
高性能 LRU 缓存。
class PerformanceCache<K, V> {
constructor(maxSize: number = 100);
get(key: K): V | undefined;
set(key: K, value: V): void;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
size(): number;
}BatchProcessor
批处理器。
class BatchProcessor {
constructor(batchSize: number = 10, delay: number = 50);
async processBatch<T, R>(
items: T[],
processor: (item: T) => Promise<R>
): Promise<R[]>;
}PerformanceTracker
性能追踪器。
class PerformanceTracker {
startTimer(name: string): void;
endTimer(name: string): number;
increment(name: string): void;
getCounter(name: string): number;
reset(): void;
getStats(): Record<string, any>;
}MemoryOptimizer
内存优化器(单例模式)。
class MemoryOptimizer {
static getInstance(): MemoryOptimizer;
addCleanupTask(task: () => void): void;
startPeriodicCleanup(intervalMs?: number): void;
runCleanup(): void;
dispose(): void;
}Debouncer
防抖器。
class Debouncer {
debounce(key: string, fn: () => void, delay: number): void;
cancel(key: string): void;
cancelAll(): void;
}工具函数 API
路径和 URL 工具
// 路径标准化
function normalizePath(path: string): string;
// 路径连接
function joinPath(...segments: string[]): string;
// 路径匹配
function matchPath(pattern: string, path: string): boolean;
// 参数提取
function extractParams(pattern: string, path: string): Record<string, string>;
// 查询参数解析
function parseQuery(search: string): Record<string, string>;
// 查询参数构建
function buildQuery(params: Record<string, any>): string;
// URL 查询参数更新
function updateQuery(url: string, params: Record<string, any>): string;数据处理工具
// 深拷贝
function deepClone<T>(obj: T): T;
// 深度合并
function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T;
// 数组去重
function unique<T>(array: T[], keyFn?: (item: T) => any): T[];
// 数组分组
function groupBy<T>(array: T[], keyFn: (item: T) => string): Record<string, T[]>;
// 数组映射为数组
function mapToArray<K, V>(map: Map<K, V>): V[];字符串工具
// 转换为驼峰命名
function toCamelCase(str: string): string;
// 转换为短横线命名
function toKebabCase(str: string): string;
// 首字母大写
function capitalize(str: string): string;
// 字符串截断
function truncate(str: string, length: number, suffix?: string): string;类型检查和安全工具
// 空值检查
function isEmpty(value: any): boolean;
// 函数检查
function isFunction(value: any): value is Function;
// Promise 检查
function isPromise(value: any): value is Promise<any>;
// 记录对象检查
function isRecord(obj: any): obj is Record<string, any>;
// 安全整数解析
function safeParseInt(value: any, defaultValue?: number): number;
// 安全执行
function safely<T>(fn: () => T, fallback: T): T;
// 安全异步执行
function safelyAsync<T>(fn: () => Promise<T>, fallback: T): Promise<T>;性能和缓存工具
// 防抖函数
function debounce<T extends (...args: any[]) => void>(
fn: T,
delay: number
): T;
// 节流函数
function throttle<T extends (...args: any[]) => void>(
fn: T,
delay: number
): T;
// 性能测量
function measureTime<T extends (...args: any[]) => any>(
fn: T,
name?: string
): T;
// 异步性能测量
function measureAsync<T>(
name: string,
fn: () => Promise<T>
): Promise<T & { duration: number }>;
// 简单缓存类
class SimpleCache<K, V> {
constructor(maxAge?: number, maxSize?: number);
set(key: K, value: V): void;
get(key: K): V | undefined;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
}🏆 最佳实践
1. I18nMessage 组件最佳实践
// ✅ 推荐:总是提供默认值
function GoodExample() {
return (
<I18nMessage
id="welcome.title"
defaultValue="欢迎"
params={{ name: '用户' }}
/>
);
}
// ❌ 避免:不提供默认值
function BadExample() {
return <I18nMessage id="welcome.title" />;
}
// ✅ 推荐:使用调试工具进行开发
function DebugExample() {
const { validateKey, debugInfo } = useI18nMessageDebug();
useEffect(() => {
// 开发环境下验证翻译键
if (process.env.NODE_ENV === 'development') {
const validation = validateKey('welcome.title');
if (!validation.exists) {
console.warn('翻译键不存在:', 'welcome.title');
}
}
}, [validateKey]);
return (
<div>
<I18nMessage id="welcome.title" defaultValue="欢迎" />
{process.env.NODE_ENV === 'development' && (
<pre>{JSON.stringify(debugInfo, null, 2)}</pre>
)}
</div>
);
}
// ✅ 推荐:使用性能优化 Hook 处理复杂逻辑
function OptimizedExample() {
const translatedText = useI18nMessageMemo(
'complex.message',
{ count: 100, type: 'items' },
'默认消息'
);
return (
<div>
<span>{translatedText}</span>
{/* 避免在渲染中直接使用 t 函数进行复杂计算 */}
</div>
);
}
// ✅ 推荐:组合使用便捷组件
function PageLayout() {
return (
<div>
<I18nTitle id="page.title" level={1} />
<I18nText id="page.subtitle" className="text-gray-600" />
<I18nMessage
id="rich.content"
format="markdown"
defaultValue="支持 **粗体** 和 *斜体*"
/>
<I18nSwitch
variant="tabs"
onLocaleChange={(locale) => {
// 可选:语言切换时的额外逻辑
console.log(`语言切换到: ${locale}`);
}}
/>
</div>
);
}
2. 国际化路由集成最佳实践
// i18n/routes.ts
import { createI18nClient, createI18nRouteLoader, createI18nHandle } from '@gulibs/vgrove-client';
// 创建国际化客户端
const i18nClient = createI18nClient({
defaultLocale: 'zh',
supportedLocales: ['zh', 'en'],
basePath: '/api/locales',
cache: true
});
// 创建国际化路由加载器
export const i18nLoader = createI18nRouteLoader(i18nClient, {
preloadAll: false,
delay: 0 // 生产环境不需要延迟
});
// 路由配置
export const routes = [
{
path: '/dashboard',
loader: i18nLoader,
handle: createI18nHandle({
meta: {
title: 'dashboard.title',
description: 'dashboard.description'
},
pageExtras: (context) => {
return <DashboardBreadcrumb path={context.path} />;
}
})
}
];
// 组件中使用
function DashboardPage() {
const { locale, isLoaded, getLocalizedUrl } = useRouteI18n();
const { t } = useI18n();
if (!isLoaded) {
return <div>加载中。..</div>;
}
return (
<div>
<h1>{t('dashboard.title')}</h1>
<button onClick={() => window.location.href = getLocalizedUrl('en')}>
Switch to English
</button>
</div>
);
}
3. 路由守卫组织
// guards/index.ts
export const authGuard = defineAuth({
redirectTo: '/login',
errorMessage: '请先登录'
});
export const adminGuard = defineAuth({
roles: ['admin'],
redirectTo: '/forbidden',
errorMessage: '需要管理员权限'
});
export const premiumGuard = defineGuard({
name: 'premium',
condition: (context) => context.user?.isPremium,
redirectTo: '/upgrade',
errorMessage: '需要升级到高级版'
});
// guards/factory.ts
export function createRoleGuard(roles: string[], redirectTo = '/forbidden') {
return defineAuth({
roles,
redirectTo,
errorMessage: `需要以下角色之一: ${roles.join(', ')}`
});
}
export function createPermissionGuard(permissions: string[], redirectTo = '/forbidden') {
return defineGuard({
name: `permission-${permissions.join('-')}`,
condition: (context) => {
return permissions.every(permission =>
context.user?.permissions?.includes(permission)
);
},
redirectTo,
errorMessage: `需要以下权限: ${permissions.join(', ')}`