React18+pnpm+Ts+React-Router v6从0-1搭建后台系统

1,080 阅读4分钟

大家好,我是鱼樱!!!

关注公众号【鱼樱AI实验室】持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~

一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享~~~

今天大家分享一个0-1搭建React后台管理系统的最佳实践模板思路~ 并且推荐一个双越老师的划水AI项目 有需要的可以私我走优惠通道~

React18/19 + pnpm + TypeScript + React-Router v6 后台管理系统最佳实践

项目基础搭建

技术栈选择

  • React 18/19:使用最新的React版本获取性能提升和新特性
  • TypeScript:提供类型安全,提升代码质量和开发体验
  • pnpm:比npm/yarn更快的包管理器,节省磁盘空间
  • React Router v6:最新版路由,支持嵌套路由和数据加载
  • Vite:比Create React App更快的构建工具

项目初始化

# 使用pnpm创建Vite+React+TS项目
pnpm create vite my-admin --template react-ts

# 进入项目目录
cd my-admin

# 安装依赖
pnpm install

# 安装React Router
pnpm add react-router-dom@6

项目目录结构

src/
├── assets/          # 静态资源
├── components/      # 公共组件
├── hooks/           # 自定义hooks
├── layouts/         # 布局组件
├── pages/           # 页面组件
├── services/        # API请求
├── stores/          # 状态管理
├── types/           # 类型定义
├── utils/           # 工具函数
├── App.tsx          # 应用入口
└── main.tsx         # 渲染入口

代码规范与工程化

ESLint 和 Prettier 配置

# 安装ESLint和Prettier
pnpm add -D eslint prettier eslint-plugin-react eslint-plugin-react-hooks
pnpm add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
pnpm add -D eslint-config-prettier eslint-plugin-prettier

.eslintrc.js

module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
    'plugin:prettier/recommended'
  ],
  plugins: ['react', '@typescript-eslint', 'prettier'],
  rules: {
    // 自定义规则
    'react/react-in-jsx-scope': 'off',
    'prettier/prettier': 'error'
  }
};

.prettierrc

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "printWidth": 100,
  "trailingComma": "es5"
}

Git 提交规范

# 安装commitlint和husky
pnpm add -D @commitlint/cli @commitlint/config-conventional husky lint-staged

commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert']
    ]
  }
};

设置husky

# 初始化husky
npx husky install

# 添加pre-commit钩子
npx husky add .husky/pre-commit "npx lint-staged"

# 添加commit-msg钩子
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

配置lint-staged

// package.json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}

编辑器规范

.editorconfig

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

VSCode设置

创建.vscode/settings.json

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true
}

路由与权限设计

React Router v6 配置

// src/routes/index.tsx
import { lazy, Suspense } from 'react';
import { Navigate, RouteObject } from 'react-router-dom';
import MainLayout from '@/layouts/MainLayout';
import AuthGuard from '@/components/AuthGuard';
import Loading from '@/components/Loading';

// 懒加载页面
const Dashboard = lazy(() => import('@/pages/Dashboard'));
const UserList = lazy(() => import('@/pages/User/List'));
const UserDetail = lazy(() => import('@/pages/User/Detail'));
const Login = lazy(() => import('@/pages/Login'));

// 路由配置
export const routes: RouteObject[] = [
  {
    path: '/',
    element: <MainLayout />,
    children: [
      { index: true, element: <Navigate to="/dashboard" replace /> },
      {
        path: 'dashboard',
        element: (
          <AuthGuard requiredPermissions={['dashboard:view']}>
            <Suspense fallback={<Loading />}>
              <Dashboard />
            </Suspense>
          </AuthGuard>
        )
      },
      {
        path: 'users',
        children: [
          {
            index: true,
            element: (
              <AuthGuard requiredPermissions={['user:list']}>
                <Suspense fallback={<Loading />}>
                  <UserList />
                </Suspense>
              </AuthGuard>
            )
          },
          {
            path: ':id',
            element: (
              <AuthGuard requiredPermissions={['user:detail']}>
                <Suspense fallback={<Loading />}>
                  <UserDetail />
                </Suspense>
              </AuthGuard>
            )
          }
        ]
      }
    ]
  },
  {
    path: '/login',
    element: (
      <Suspense fallback={<Loading />}>
        <Login />
      </Suspense>
    )
  },
  { path: '*', element: <Navigate to="/login" replace /> }
];

权限设计

基于RBAC的权限设计

// src/components/AuthGuard.tsx
import { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';

interface AuthGuardProps {
  children: ReactNode;
  requiredPermissions?: string[];
}

const AuthGuard = ({ children, requiredPermissions = [] }: AuthGuardProps) => {
  const { isAuthenticated, hasPermissions } = useAuth();

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  if (requiredPermissions.length > 0 && !hasPermissions(requiredPermissions)) {
    return <Navigate to="/403" replace />;
  }

  return <>{children}</>;
};

export default AuthGuard;

权限相关的Hook

// src/hooks/useAuth.ts
import { useCallback } from 'react';
import { useAuthStore } from '@/stores/authStore';

export const useAuth = () => {
  const { user, permissions, setUser, logout } = useAuthStore();

  const isAuthenticated = !!user;

  const hasPermissions = useCallback(
    (requiredPermissions: string[]) => {
      if (!permissions || permissions.length === 0) return false;
      return requiredPermissions.every(permission => permissions.includes(permission));
    },
    [permissions]
  );

  return {
    user,
    isAuthenticated,
    permissions,
    hasPermissions,
    setUser,
    logout
  };
};

状态管理:Zustand vs Redux

比较

特性ZustandRedux (Redux Toolkit)
上手难度简单中等
样板代码中等 (RTK减少了大量样板代码)
体积轻量较重
中间件支持有,但不太丰富丰富
开发工具有基础支持强大
异步处理直接支持需要RTK Query或thunk
社区生态成长中成熟

选择建议

  • Zustand:适合中小型项目,需要快速开发,简单状态管理
  • Redux:适合大型项目,有复杂状态逻辑,需要强大的调试工具和中间件支持

Zustand最佳实践

// src/stores/authStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface User {
  id: string;
  name: string;
}

interface AuthState {
  user: User | null;
  permissions: string[];
  setUser: (user: User, permissions: string[]) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      permissions: [],
      setUser: (user, permissions) => set({ user, permissions }),
      logout: () => set({ user: null, permissions: [] })
    }),
    {
      name: 'auth-storage'
    }
  )
);
// src/stores/userStore.ts
import { create } from 'zustand';
import { fetchUsers } from '@/services/userService';

interface UserState {
  users: any[];
  loading: boolean;
  error: string | null;
  fetchUsers: () => Promise<void>;
}

export const useUserStore = create<UserState>((set) => ({
  users: [],
  loading: false,
  error: null,
  fetchUsers: async () => {
    set({ loading: true, error: null });
    try {
      const data = await fetchUsers();
      set({ users: data, loading: false });
    } catch (error) {
      set({ error: (error as Error).message, loading: false });
    }
  }
}));

Redux Toolkit最佳实践

// src/stores/slices/authSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface User {
  id: string;
  name: string;
}

interface AuthState {
  user: User | null;
  permissions: string[];
}

const initialState: AuthState = {
  user: null,
  permissions: []
};

export const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setUser: (state, action: PayloadAction<{ user: User; permissions: string[] }>) => {
      state.user = action.payload.user;
      state.permissions = action.payload.permissions;
    },
    logout: (state) => {
      state.user = null;
      state.permissions = [];
    }
  }
});

export const { setUser, logout } = authSlice.actions;
export default authSlice.reducer;
// src/stores/slices/userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { fetchUsers } from '@/services/userService';

export const fetchUsersThunk = createAsyncThunk('users/fetchUsers', async () => {
  const response = await fetchUsers();
  return response;
});

const userSlice = createSlice({
  name: 'users',
  initialState: {
    data: [],
    loading: false,
    error: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsersThunk.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUsersThunk.fulfilled, (state, action) => {
        state.data = action.payload;
        state.loading = false;
      })
      .addCase(fetchUsersThunk.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message || 'Failed to fetch users';
      });
  }
});

export default userSlice.reducer;

UI库选择

主流UI库对比

UI库优点缺点适用场景
Ant Design组件丰富,企业级,生态成熟体积较大,定制化需要额外工作后台管理系统,企业应用
Material UI设计精美,响应式,支持深度定制学习曲线略陡,体积较大需要精美UI的应用,偏向Google设计风格
Chakra UI轻量级,易于定制,响应式组件相对较少中小型项目,需要高度定制化
Tailwind UI高度可定制,不限制组件结构需要自己实现交互逻辑需要独特设计的项目
Arco Design轻量级,性能优秀,全局配置便捷社区相对较小注重性能的后台系统

推荐选择

对于后台管理系统,推荐Ant DesignArco Design:

# 安装Ant Design
pnpm add antd @ant-design/icons

# 或者安装Arco Design
pnpm add @arco-design/web-react @arco-design/icon

API请求封装

使用Axios封装

pnpm add axios
// src/utils/request.ts
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { message } from 'antd';
import { useAuthStore } from '@/stores/authStore';

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    const token = useAuthStore.getState().user?.token;
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    const { data } = response;
    // 根据实际后端接口结构调整
    if (data.code !== 0) {
      message.error(data.message || '请求错误');
      // 处理特定的错误码
      if (data.code === 401) {
        useAuthStore.getState().logout();
      }
      return Promise.reject(new Error(data.message || '请求错误'));
    }
    return data.data;
  },
  (error: AxiosError) => {
    if (error.response) {
      const status = error.response.status;
      let errorMsg = '未知错误';
      
      if (status === 401) {
        errorMsg = '未授权,请重新登录';
        useAuthStore.getState().logout();
      } else if (status === 403) {
        errorMsg = '拒绝访问';
      } else if (status === 404) {
        errorMsg = '请求的资源不存在';
      } else if (status === 500) {
        errorMsg = '服务器错误';
      }
      
      message.error(errorMsg);
    } else {
      message.error('网络错误,请检查您的网络连接');
    }
    return Promise.reject(error);
  }
);

// 封装GET请求
export function get<T>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
  return service.get(url, { params, ...config });
}

// 封装POST请求
export function post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
  return service.post(url, data, config);
}

// 封装其他请求方法
export function put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
  return service.put(url, data, config);
}

export function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
  return service.delete(url, config);
}

export default service;

性能优化策略

  1. 代码分割:使用React.lazy和Suspense进行组件懒加载
  2. 虚拟列表:对长列表使用react-window或react-virtualized
  3. 组件缓存:使用React.memo、useMemo、useCallback避免不必要的重渲染
  4. Web Vitals监控:使用web-vitals库监控核心性能指标
  5. 资源优化:使用现代图片格式(WebP),合理使用CDN
// src/hooks/useVirtualScroll.ts
import { useState, useRef, useEffect } from 'react';

export function useVirtualScroll<T>(items: T[], itemHeight: number, containerHeight: number) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);

  const visibleItemCount = Math.ceil(containerHeight / itemHeight) + 2;
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(startIndex + visibleItemCount, items.length);

  const visibleItems = items.slice(startIndex, endIndex);
  const totalHeight = items.length * itemHeight;
  const offsetY = startIndex * itemHeight;

  useEffect(() => {
    const handleScroll = () => {
      if (containerRef.current) {
        setScrollTop(containerRef.current.scrollTop);
      }
    };

    const container = containerRef.current;
    container?.addEventListener('scroll', handleScroll);
    return () => container?.removeEventListener('scroll', handleScroll);
  }, []);

  return {
    containerRef,
    visibleItems,
    totalHeight,
    offsetY,
  };
}

总结

搭建一个现代化的React后台管理系统,推荐以下选择:

  1. 基础技术栈:React 18/19 + TypeScript + pnpm + Vite
  2. 路由:React Router v6
  3. 状态管理
    • 中小型项目:Zustand
    • 大型复杂项目:Redux Toolkit
  4. UI库
    • 企业级后台系统:Ant DesignArco Design
    • 注重定制化:Chakra UI或基于Tailwind CSS的组件库
  5. 工程化
    • ESLint + Prettier + Husky + Commitlint
    • 自动化测试:Jest + React Testing Library
  6. 构建工具:Vite(比CRA更快的开发体验)

以上最佳实践能够构建出一个现代化、可维护、高性能的React后台管理系统。根据项目具体需求,可以进行适当调整和优化。

哥哥姐姐弟弟妹妹们都看到这里不给个赞👍🏻