# 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 (
{collapsed ? null : React Admin}
);
}
```
更新 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 (
);
}
```
6. 添加路由切换页面淡入淡出动画
安装 framer-motion
```js
npm install framer-motion
```
封装PageAnimate组件:
```tsx
import type { PropsWithChildren } from "react";
import { AnimatePresence, motion } from "framer-motion";
export function PageAnimate({ children }: PropsWithChildren) {
return (
{children}
);
}
```
将 layouts/index.tsx 中的 content 拆分出来
```tsx
import { Outlet, useMatches } from "react-router-dom";
import { Layout } from "antd";
import { PageAnimate } from "@/components/page-animate";
export default function Content() {
const matches = useMatches();
const currRouter = matches.at(-1);
return (
{/* Outlet是子路由的占位符 */}
);
}
```
# 添加 @tanstack/react-query
用于处理 React 中获取、缓存和更新异步数据
自动缓存+重新获取+窗口重新聚焦+轮询/实时
并行+依赖查询
突变+反应式查询重取
多层缓存+自动垃圾回收
分页+基于游标的查询
无限滚动查询/滚动恢复
请求取消
支持 React Suspense
支持预查询
```js
npm add @tanstack/react-query @tanstack/react-query-devtools
```
修改 main.tsx
```tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // 窗口聚焦时重新获取数据
refetchOnReconnect: false, // 网络重新连接时重新获取数据
retry: false, // 失败时重试
},
},
});
createRoot(document.getElementById("root")!).render(
,
);
```