UmiJS Max 终极实战:动态路由 + 权限管理 + Loading + Zustand(超详细代码)

大家好,我是前端小尧!今天我们把 UmiJS Max 的权限管理玩到极致:动态路由、无权限跳转、按钮级权限、模块化逻辑,还加了 Loading 组件 避免跳转闪烁,用 Zustand 管理角色状态告别刷新。代码案例丰富到爆,赶紧收藏起来吧!

一、目标:打造丝滑的权限系统

我们要实现:

  1. 动态路由:用 patchRoutes 根据角色生成菜单,支持后端返回。
  2. 无权限跳转:用 render 拦截,跳转到首个有权页面。
  3. 按钮级权限:用 Access 和 useAccess 精细化控制。
  4. 模块化:权限逻辑抽到 accessHelper.ts,复用性强。
  5. Loading 组件:跳转时显示加载动画,避免闪烁。
  6. Zustand 状态管理:动态切换角色,无需刷新。
二、项目准备:安装依赖

先安装 Zustand:

npm install zustand

启用 Umi 的 access 插件:

// .umirc.ts
export default {
  access: {},
};
三、模块化权限逻辑:accessHelper.ts

权限逻辑抽到 src/utils/accessHelper.ts:

// src/utils/accessHelper.ts
export const defineAccess = (role: string | undefined) => {
  return {
    canAdmin: role === 'admin',
    canUser: role === 'user',
    canRead: role === 'admin' || role === 'user',
    canDelete: role === 'admin', // 按钮级权限
  };
};

// 路由权限映射表
export const routeAccessMap = {
  '/home': ['canRead'],
  '/admin': ['canAdmin'],
  '/user': ['canUser'],
};

// 检查是否有权限访问路径
export const hasAccessToRoute = (path: string, access: any) => {
  const requiredAccess = routeAccessMap[path] || [];
  return requiredAccess.every((key) => access[key]);
};

// 找到首个有权限的页面
export const getFirstAccessibleRoute = (access: any) => {
  return Object.keys(routeAccessMap).find((path) => hasAccessToRoute(path, access)) || '/home';
};

// 动态路由
export const getDynamicRoutes = (role: string) => {
  const allRoutes = [
    { path: '/home', component: '@/pages/home', access: 'canRead' },
    { path: '/admin', component: '@/pages/admin', access: 'canAdmin' },
    { path: '/user', component: '@/pages/user', access: 'canUser' },
  ];
  return allRoutes.filter((route) => {
    if (!route.access) return true;
    return role === 'admin' ? true : route.access !== 'canAdmin';
  });
};

src/access.ts 引用:

// src/access.ts
import { defineAccess } from '@/utils/accessHelper';

export default function access(initialState: { role?: string } | undefined) {
  return defineAccess(initialState?.role);
}
四、Zustand 管理角色状态

新建 src/store/roleStore.ts 用 Zustand 管理角色:

// src/store/roleStore.ts
import { create } from 'zustand';
import { getDynamicRoutes } from '@/utils/accessHelper';

interface RoleState {
  role: string;
  routes: any[];
  setRole: (role: string) => void;
}

export const useRoleStore = create<RoleState>((set) => ({
  role: 'user', // 默认角色
  routes: getDynamicRoutes('user'), // 默认路由
  setRole: (role) => set((state) => ({
    role,
    routes: getDynamicRoutes(role),
  })),
}));

亮点:角色和路由状态统一管理,切换角色时无需刷新。

五、动态路由:用 patchRoutes 生成菜单

在 src/app.tsx 中实现动态路由和跳转逻辑:

// src/app.tsx
import { history, IRoute } from 'umi';
import { defineAccess, hasAccessToRoute, getFirstAccessibleRoute, getDynamicRoutes } from '@/utils/accessHelper';
import { useRoleStore } from '@/store/roleStore';

export async function getInitialState() {
  return { role: 'user' }; // 模拟后端返回
}

// 动态路由
export const patchRoutes = ({ routes }: { routes: IRoute[] }) => {
  const { role } = useRoleStore.getState();
  const dynamicRoutes = getDynamicRoutes(role);
  routes.length = 0;
  routes.push(...dynamicRoutes);
};

// 无权限跳转 + Loading
let isRendering = false;
export const render = (oldRender: () => void) => {
  const renderWithAccess = () => {
    if (isRendering) return;
    isRendering = true;
    oldRender();

    const { role } = useRoleStore.getState();
    const access = defineAccess(role);
    const currentPath = window.location.pathname;

    if (!hasAccessToRoute(currentPath, access)) {
      const redirectPath = getFirstAccessibleRoute(access);
      history.replace(redirectPath);
    }
    isRendering = false;
  };
  renderWithAccess();
};

注意:isRendering 防止重复渲染,配合 Loading 使用。

六、Loading 组件:避免跳转闪烁

新建 src/components/Loading.tsx:

// src/components/Loading.tsx
import { Spin } from 'antd';

export default () => (
  <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
    <Spin tip="加载中..." size="large" />
  </div>
);

在 src/app.tsx 中引入 Loading:

// src/app.tsx (续)
import { useState } from 'react';
import Loading from '@/components/Loading';

export const rootContainer = (container: React.ReactNode) => {
  const [loading, setLoading] = useState(true);

  setTimeout(() => setLoading(false), 500); // 模拟加载延迟

  return loading ? <Loading /> : container;
};

效果:跳转时显示 Loading,避免页面闪烁。

七、按钮级权限:页面实现

在 /home 页面实现角色切换和按钮控制:

// src/pages/home.tsx
import { useAccess, Access } from 'umi';
import { useRoleStore } from '@/store/roleStore';
import { history } from 'umi';

export default () => {
  const access = useAccess();
  const { role, setRole } = useRoleStore();

  const switchRole = (newRole: string) => {
    setRole(newRole);
    const newAccess = defineAccess(newRole);
    const currentPath = window.location.pathname;
    if (!hasAccessToRoute(currentPath, newAccess)) {
      const redirectPath = getFirstAccessibleRoute(newAccess);
      history.push(redirectPath); // 使用 push 代替刷新
    }
  };

  return (
    <div>
      <h1>欢迎来到首页 (当前角色: {role})</h1>
      <button onClick={() => switchRole('admin')}>切换为管理员</button>
      <button onClick={() => switchRole('user')}>切换为普通用户</button>

      <button>查看详情</button>
      <Access accessible={access.canDelete} fallback={<span>无删除权限</span>}>
        <button onClick={() => alert('删除成功')}>删除</button>
      </Access>
      {access.canAdmin && <button>管理员专用按钮</button>}
    </div>
  );
};

其他页面:

// src/pages/admin.tsx
export default () => <div>管理员专属页面</div>;

// src/pages/user.tsx
export default () => <div>普通用户专属页面</div>;

效果

  • 点击切换角色,路由和权限实时更新,无需刷新。
  • role: 'user' 无删除权限,访问 /admin 跳到 /user。
  • Loading 在跳转时显示,体验丝滑。
八、总结:六大功能完美实现
  1. 动态路由:patchRoutes 根据角色生成菜单。
  2. 无权限跳转:render 拦截,跳转首个有权页面。
  3. 按钮级权限:Access 和 useAccess 精细控制。
  4. 模块化:accessHelper.ts 抽离逻辑。
  5. Loading 组件:rootContainer 加加载动画。
  6. Zustand:角色状态管理,无刷新切换。

小贴士

  • Loading 时间可根据接口响应调整。
  • Zustand 还能加持久化(persist 中间件)。

今天的分享干货满满吧!代码直接跑起来就行,觉得有用记得点赞收藏,有问题留言讨论哦~下次聊聊 Umi 的更多玩法,敬请期待!

<think>好的,用户现在问的是基于Next.js的最佳实践。我需要整理一下Next.js的常见最佳实践,同时考虑用户可能的背景和需求。首先,用户之前已经询问过脚手架,所以可能是在选择Next.js作为他们的项目基础,现在需要知道如何正确使用它。 用户可能是刚接触Next.js的开发者,或者有一定经验但想优化项目结构。他们可能想知道如何组织代码、性能优化、SEO处理、配置等方面的最佳实践。需要涵盖项目结构、路由、数据获取、静态生成、服务端渲染、API路由、性能优化、错误处理、部署等。 还要注意用户可能关心的点,比如如何提高页面加载速度,如何管理状态,如何集成TypeScript,如何配置环境变量,以及如何部署到不同的平台。此外,可能还需要提到常用的工具库或插件,比如SWR、next-auth、next-sitemap等,来增强项目功能。 需要结构化地分点说明,每个部分给出具体的做法和示例代码,比如使用动态路由时如何组织文件,数据获取时优先使用getStaticProps和getServerSideProps的场景,如何优化图片等。同时,要提醒常见的陷阱,比如避免在客户端渲染时直接访问window对象,或者如何处理静态资源的路径。 还需要考虑中大型项目的实践,比如使用模块化CSS,状态管理库的选择,如何拆分组件,以及测试策略。部署方面,Vercel是最佳选择,但也要提到其他平台的可能配置。最后,可以推荐一些学习资源或工具,帮助用户进一步深入。</think>以下是一份基于 Next.js 14+ 的企业级最佳实践指南,结合现代 Web 开发趋势和性能优化经验: --- ### 一、项目架构规范 ```bash /src ├── app # App Router 模式(推荐) │ ├── (auth) # 路由组 │ ├── api # API 路由 │ ├── dashboard # 业务模块 │ │ └── layout.tsx # 嵌套布局 ├── components # 通用组件 │ ├── ui # 基础 UI 组件(原子设计) │ └── widgets # 业务组件 ├── lib # 工具库 │ ├── api # API 客户端封装 │ └── utils # 工具函数 ├── styles # 全局样式 ├── types # TypeScript 类型定义 └── public # 静态资源 ``` --- ### 二、核心实践策略 1. **路由策略优化** - 使用 **App Router** 替代 Pages Router(支持 React Server Components) - 动态路由参数规范:`/products/[slug]/page.tsx` - 路由分组:`(marketing)/about/page.tsx` 保持 URL 结构整洁 2. **数据获取规范** ```tsx // 优先使用服务端组件 async function Page() { const data = await fetch('https://...', { next: { revalidate: 3600 } }); return <Component data={data} /> } ``` 3. **渲染策略矩阵** | 场景 | 推荐方案 | 代码示例 | |---------------------|--------------------------|------------------------------| | 静态内容 | generateStaticParams | 预生成 1000 个商品页 | | 动态 SEO 内容 | getStaticProps + revalidate | ISR 增量静态再生 | | 用户个性化内容 | 客户端数据获取 | SWR + API Routes | --- ### 三、性能优化方案 1. **图片优化** ```tsx import Image from 'next/image'; // WebP 格式 + 尺寸优化 <Image src="/image.jpg" alt="描述" width={1200} height={800} placeholder="blur" blurDataURL="data:image/png;base64..." /> ``` 2. **代码拆分** ```tsx // 动态导入非关键组件 const DynamicChart = dynamic(() => import('./Chart'), { ssr: false, loading: () => <Skeleton /> }); ``` 3. **缓存策略** ```ts // next.config.js module.exports = { headers: async () => [ { source: '/(.*)', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' } ] } ] }; ``` --- ### 四、安全实践 1. **API 安全防护** ```ts // app/api/route.ts export async function POST(req: Request) { // CSRF 校验 const csrfToken = req.headers.get('X-CSRF-Token'); if (!verifyCsrfToken(csrfToken)) { return new Response('Invalid CSRF token', { status: 403 }); } } ``` 2. **环境变量管理** ```env # .env.local NEXT_PUBLIC_API_URL=https://api.example.com # 客户端可见 DATABASE_URL=postgres://... # 仅服务端可见 ``` --- ### 五、进阶配置 1. **中间件优化** ```ts // middleware.ts export function middleware(request: NextRequest) { // 智能重定向 if (request.nextUrl.pathname === '/legacy') { return NextResponse.redirect(new URL('/modern', request.url)); } } ``` 2. **错误边界处理** ```tsx // app/error.tsx(全局错误页面) 'use client'; export default function ErrorBoundary({ error }: { error: Error }) { return ( <div> <h2>发生错误: {error.message}</h2> <button onClick={() => reset()}>重试</button> </div> ); } ``` --- ### 六、部署优化 1. **Vercel 专属优化** ```js // vercel.json { "headers": [ { "source": "/(.*)", "headers": [ { "key": "X-Frame-Options", "value": "DENY" } ] } ], "experimental": { "isrMemory": 1000 // 调整 ISR 缓存内存 } } ``` 2. **监控集成** - 使用 `@vercel/speed-insights` 和 `@vercel/analytics` - 错误监控推荐 Sentry 或 LogRocket --- ### 七、推荐技术栈组合 | 领域 | 推荐方案 | 优势 | |---------------|----------------------------|-------------------------------| | 状态管理 | Zustand / Jotai | 轻量级原子化状态 | | 样式方案 | Tailwind CSS + CSS Modules | 原子化+模块化结合 | | 表单处理 | React Hook Form + Zod | 高性能校验 | | 数据请求 | SWR + Axios | 缓存策略+拦截器 | | 测试方案 | Cypress + Vitest | E2E + 单元测试组合 | --- ### 关键性能指标(参考值) - **LCP** (最大内容绘制): < 2.5s - **FCP** (首次内容绘制): < 1.8s - **TTFB** (首字节时间): < 600ms - **Bundle Size**: 主包 < 150kB 通过以上实践,可使 Next.js 应用在 Lighthouse 测试中稳定获得 90+ 评分,同时保持优异的 SEO 表现。建议根据具体业务需求调整优化策略,定期使用 `next build --profile` 进行性能分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值