# react-template **Repository Path**: sun-chaoqun/react-template ## Basic Information - **Project Name**: react-template - **Description**: react19 项目模板 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-02-20 - **Last Updated**: 2025-07-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 项目搭建 npm create vite@latest 选择: React + Typescript + SWC 安装完毕之后拥有如下依赖: ```json "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/js": "^9.19.0", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^9.19.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.18", "globals": "^15.14.0", "typescript": "~5.7.2", "typescript-eslint": "^8.22.0", "vite": "^6.1.0" } ``` 1. 添加 eslint + prettier ``` npm install --save-dev eslint-plugin-prettier eslint-config-prettier npm install --save-dev --save-exact prettier ``` eslint-plugin-prettier: 这个插件允许你将Prettier的规则集成到ESLint中。这意味着你可以使用ESLint来运行Prettier的规则检查,而无需单独运行Prettier。 当你运行ESLint时,如果代码不符合Prettier的格式要求,eslint-plugin-prettier 会报告这些问题,就像它们是ESLint规则违规一样。 这有助于确保你的代码风格与Prettier的规则保持一致,同时仍然利用ESLint的强大功能来检查代码质量和潜在的错误。 eslint-config-prettier: 这个配置包用于禁用所有不必要的或可能与Prettier冲突的ESLint规则。 当Prettier负责代码格式化时,最好避免使用可能与Prettier规则冲突的ESLint规则。eslint-config-prettier 自动为你处理这个问题,确保你的ESLint配置不会干扰Prettier的工作。 通过使用 eslint-config-prettier,你可以确保你的代码风格完全由Prettier控制,而ESLint则专注于代码质量和潜在错误的检查。 --save-dev 参数指示npm将这些包作为开发依赖项添加到项目的 package.json 文件中。这意味着这些包仅在开发过程中需要,而在生产环境中不需要。 --save-exact 当你在运行 npm install 命令时添加 --save-exact 选项,npm会将安装的依赖包版本精确锁定到指定的版本号,而不会添加 ^ 或 ~ 这样的版本范围运算符。 > 修改 eslint.config.js > 这样就启用了 prettier/prettier 规则,也启用 eslint-config-prettier 配置(关闭 Prettier 和 ESLint 规则冲突) ``` import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; export default tseslint.config({ extends: [js.configs.recommended, ...tseslint.configs.recommended, eslintPluginPrettierRecommended], // 其他配置... }) ``` > 在根目录下添加 .prettierrc 配置文件 ```json { "arrowParens": "always", "bracketSameLine": false, "bracketSpacing": true, "semi": true, "experimentalTernaries": false, "singleQuote": false, "jsxSingleQuote": false, "quoteProps": "as-needed", "trailingComma": "all", "singleAttributePerLine": false, "htmlWhitespaceSensitivity": "css", "proseWrap": "preserve", "insertPragma": false, "printWidth": 110, "requirePragma": false, "tabWidth": 2, "useTabs": false, "embeddedLanguageFormatting": "auto" } ``` > 配置 vscode 自动保存格式化:根目录下新建 .vscode/settings.json ```json { "editor.codeActionsOnSave": { "source.fixAll": "explicit" }, "editor.formatOnSave": true } ``` 2. 添加 husky + lint-staged 初始化git ```js git init ``` 安装 husky + lint-staged ```js npm install --save-dev husky lint-staged npx husky init ``` 在 package.josn 文件添加 lint-staged 配置 ```json { // ... "scripts": { // ... + "lint:fix": "eslint . --fix" }, + "lint-staged": { + "src/**/*.{ts,tsx,js}": [ + "npm run lint:fix" + ] + } } ``` 修改 .husky/pre-commit 文件 ```js #!/usr/bin/env sh npx --no-install lint-staged ``` --no-install 是一个npx的选项,它告诉npx不要尝试安装缺失的包。如果指定的包(在这个例子中是lint-staged)没有在项目的node_modules/.bin目录中找到,npx将会报错而不是尝试安装它。 3. 配置别名 npm install --save-dev @types/node 修改 vite.config.ts 文件 ```js import path from "node:path"; import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, }); ``` 4. 配置环境变量 创建 .env 文件,写入如下内容: ``` VITE_APP_BASE_URL="/" VITE_APP_API_URL="//127.0.0.1:5050/api" ``` 修改 vite-env.d.ts ```ts /// interface ImportMetaEnv { readonly VITE_APP_BASE_URL: string; readonly VITE_APP_API_URL: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` 5. 管理 import 顺序 ```js npm install --save-dev eslint-plugin-simple-import-sort ``` 更新 eslint.config.js 文件 ```js { plugins: { "simple-import-sort": simpleImportSort, }, rules: { "simple-import-sort/exports": "error", "simple-import-sort/imports": [ "error", { groups: [ [ "^(node:|vite)", "^react", "^@?\\w", "^@/components", "^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$", "^@(utils|store|hooks|api|router)", ], ["antd/locale/zh_CN", "dayjs/locale/zh-cn"], ["^.+\\.s?css$"], ], }, ], }, }, ``` # 添加 tailwindcss ```js npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p --ts ``` npx tailwindcss init -p --ts 报错,由于自动安装的版本是 4.0.3,但是官网的版本是3.4.17. ```js npm install -D tailwindcss@3.4.17 ``` 修改 tailwind.config.ts ```js export default { darkMode: "class", content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], // ... }; ``` 修改 index.css ```js @tailwind base; @tailwind components; @tailwind utilities; ``` 更新 .vscode/settings.json,忽略 tailwind 带来的未知 css 规则的警告 ``` { "css.lint.unknownAtRules": "ignore" } ``` # 添加路由 react-router v6 ```js npm install react-router-dom@6 ``` 1. 新增文件 src/layouts/index.tsx ```tsx import { Outlet } from "react-router-dom"; export default function MainLayout() { return ( <> {/* Outlet是子路由的占位符 */} ); } ``` 2. 新增文件 src/router/index.tsx ```tsx import { createBrowserRouter, Navigate, type RouteObject } from "react-router-dom"; const routes: RouteObject[] = [ { path: "/", lazy: async () => ({ Component: (await import("@/layouts")).default, }), children: [ { index: true, element: , }, { path: "landing", lazy: async () => ({ Component: (await import("@/pages/landing")).default, }), }, ], }, ]; export const router = createBrowserRouter(routes, { basename: import.meta.env.VITE_APP_BASE_URL, }); ``` 3. 新增文件 src/pages/landing/index.tsx ```tsx export default function LandingPage() { return
Landing Page
; } ``` 4. 修改 src/App.tsx 内容: ```tsx import { RouterProvider } from "react-router-dom"; import { router } from "@/router"; export default function App() { return Loading...} />; } ``` 启动项目,就能看到 LandingPage 页面 # 添加 Antd + dayjs + zustand ```js npm install antd dayjs zustand ``` > 修改 antd 的语言配置用到 dayjs > 用全局状态管理管理主题色,全局状态管理用的 zustand 1. 配置antd 修改 App.tsx ```tsx import { RouterProvider } from "react-router-dom"; import { legacyLogicalPropertiesTransformer, StyleProvider } from "@ant-design/cssinjs"; import { App as AntdApp, ConfigProvider, theme as antdTheme } from "antd"; import { useTheme } from "./components/theme-provider"; import { useSettingsStore } from "./stores/settings"; import zhCN from "antd/locale/zh_CN"; import "dayjs/locale/zh-cn"; import { router } from "@/router"; function App() { const { isDarkMode } = useTheme(); const colorPrimary = useSettingsStore((state) => state.colorPrimary); console.log("isDarkMode", isDarkMode); return ( ); } export default App; ``` 2. 新建项目设置 settings store 新建 src/stores/settings.ts 文件,存放全局设置 ```tsx import { create } from "zustand"; import { persist } from "zustand/middleware"; interface SettingsState { colorPrimary: string; setColorPrimary: (value: string) => void; collapsed: boolean; setCollapsed: (value: boolean) => void; } export const useSettingsStore = create()( persist( (set) => ({ colorPrimary: "#1DA57A", setColorPrimary: (colorPrimary) => set({ colorPrimary }), collapsed: false, setCollapsed: (collapsed) => set({ collapsed }), }), { name: "app-settings", }, ), ); ``` 3. 添加自定义主题色 修改 pages/landing/index.tsx ```tsx import { Button, ColorPicker, DatePicker, Flex, Typography } from "antd"; import { useSettingsStore } from "@/stores/settings"; const { RangePicker } = DatePicker; export default function LandingPage() { const colorPrimary = useSettingsStore((state) => state.colorPrimary); const setColorPrimary = useSettingsStore((state) => state.setColorPrimary); return (
Landing Page {/* */} { setColorPrimary(color.toHex()); }} />
); } ``` 4. 添加 antd message等静态方法 新建 src/components/static-antd/index.tsx ```tsx import { App } from "antd"; import type { MessageInstance } from "antd/es/message/interface"; import type { ModalStaticFunctions } from "antd/es/modal/confirm"; import type { NotificationInstance } from "antd/es/notification/interface"; let message: MessageInstance; let notification: NotificationInstance; let modal: Omit; export default function StaticAntd() { const staticFunction = App.useApp(); // window.$message = staticFunction.message; // window.$modal = staticFunction.modal; // window.$notification = staticFunction.notification; message = staticFunction.message; modal = staticFunction.modal; notification = staticFunction.notification; return null; } // eslint-disable-next-line react-refresh/only-export-components export { message, modal, notification }; ``` 5. 布局 安装 antd icons ```tsx npm install @ant-design/icons --save ``` 新建 src/layouts/sider/index.tsx ```tsx import { useEffect, useRef, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { AreaChartOutlined, HomeOutlined, UserOutlined, VideoCameraOutlined } from "@ant-design/icons"; import { Layout, Menu, type MenuProps } from "antd"; import { useTheme } from "@/components/theme-provider"; import ReactIcon from "@/assets/react.svg"; import { useSettingsStore } from "@/stores/settings"; // 递归函数,找到匹配的菜单项 const findSelectedKeys = (items: MenuProps["items"], pathname: string, path: string[] = []) => { const selectedKeys: string[] = []; let openKeys: string[] = []; const travel = (items: MenuProps["items"], pathname: string, path: string[]) => { for (const item of items!) { if (item!.key === pathname) { selectedKeys.push(item!.key); openKeys = [...path]; return; } if ((item as any).children) { path.push(item!.key as string); travel((item as any).children, pathname, path); path.pop(); } } }; travel(items, pathname, path); return { selectedKeys, openKeys }; }; const items: MenuProps["items"] = [ { icon: , label: 首页, key: "/landing", }, { icon: , label: 用户管理, key: "/user-management", }, { icon: , label: "一级菜单", key: "/nav", children: [ { key: "/nest-menu/sub-menu-1", label: 二级菜单-1, }, { key: "/nest-menu/sub-menu-2", label: 二级菜单-2, }, ], }, { icon: , label: 图表, key: "/echarts", }, ]; export default function Sider() { const location = useLocation(); const firstRenderRef = useRef(true); const [selectedKeys, setSelectedKeys] = useState([]); const [openKeys, setOpenKeys] = useState([]); const collapsed = useSettingsStore((state) => state.collapsed); const { isDarkMode } = useTheme(); useEffect(() => { if (location.pathname === "/") return; // 首次渲染时,设置默认值 if (firstRenderRef.current) { const { selectedKeys, openKeys } = findSelectedKeys(items, location.pathname); setSelectedKeys(selectedKeys); setOpenKeys(openKeys); } // 将首次渲染标记设置为false firstRenderRef.current = false; }, [location.pathname]); return ( React logo {collapsed ? null : React Admin} setSelectedKeys(selectedKeys)} openKeys={openKeys} onOpenChange={(openKeys) => setOpenKeys(openKeys)} className="!border-e-0" /> ); } ``` 更新 src/layouts/index.tsx ```tsx import { useNavigate } from "react-router-dom"; import { LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons"; import { Button, Flex, Layout } from "antd"; import { ThemeSwitch } from "@/components/theme-switch"; import Breadcrumb from "./components/bread-crumb"; import UserAvatar from "./components/user-avatar"; import Content from "./content"; import Sider from "./sider"; import { useSettingsStore } from "@/stores/settings"; export default function MainLayout() { const navigate = useNavigate(); const collapsed = useSettingsStore((state) => state.collapsed); const setCollapsed = useSettingsStore((state) => state.setCollapsed); return (