React+TS 项目结构(自学项目用)

一、标准 React + TS 项目目录结构(基于 Vite 构建)

通用的目录骨架(适配 React 18 + TypeScript 5 + Vite 5),后续逐个解析:

plain 复制代码
your-project/
├── public/                # 静态资源(不经过打包)
├── src/
│   ├── api/               # 接口请求封装
│   ├── assets/            # 静态资源(经过打包,如图片、样式)
│   ├── components/        # 通用组件(全局/业务无关)
│   │   ├── common/        # 基础组件(按钮、输入框、弹窗)
│   │   └── business/      # 业务通用组件(如订单卡片、用户信息栏)
│   ├── hooks/             # 自定义 Hooks
│   ├── layouts/           # 布局组件(全局布局、分栏布局)
│   ├── pages/             # 页面组件(路由对应的页面)
│   │   ├── Home/          # 首页
│   │   ├── User/          # 用户模块
│   │   │   ├── List/      # 用户列表页
│   │   │   └── Detail/    # 用户详情页
│   │   └── NotFound/      # 404页面
│   ├── router/            # 路由配置
│   ├── store/             # 状态管理(Redux/TanStack Query/Zustand)
│   ├── types/             # 全局类型定义(TS)
│   ├── utils/             # 工具函数
│   ├── App.tsx            # 根组件
│   ├── main.tsx           # 入口文件
│   └── vite-env.d.ts      # Vite TS 类型声明
├── .eslintrc.cjs          # ESLint 配置
├── tsconfig.json          # TypeScript 配置
├── vite.config.ts         # Vite 配置
└── package.json           # 依赖管理

二、各文件夹 / 文件解析(附代码 + 知识点关联)

1. public/- 静态资源(不打包)

作用

存放无需经过 Vite 打包的静态资源,直接复制到打包后的 dist 根目录,访问路径为 /文件名

常见文件

  • favicon.ico:网站图标
  • robots.txt:搜索引擎爬虫规则
  • index.html:HTML 入口模板(Vite 会注入打包后的 JS/CSS)

知识点关联

  • React 是「SPA 单页应用」,仅需一个 HTML 入口;
  • 静态资源的两种加载方式:public(绝对路径) vs src/assets(相对路径 / 打包处理)。

2. src/assets/- 静态资源(需打包)

作用

存放需要经过 Vite 编译 / 优化的静态资源(图片、字体、全局样式),打包后会被哈希命名(防止缓存)。

示例代码(使用图片)

tsx

tsx 复制代码
// src/pages/Home/Home.tsx
import React from 'react';
import logo from '@/assets/logo.png'; // 别名@指向src

const Home = () => {
  return <img src={logo} alt="logo" />; // 打包后会替换为哈希后的路径
};

export default Home;

知识点关联

  • 模块导入资源 :Vite/Webpack 支持将资源作为模块导入,TS 需在 vite-env.d.ts 中声明类型;
  • 样式模块化 :若存放 CSS/SCSS,可结合 CSS Modules 避免样式污染(对应 React 组件样式隔离知识点)。

3. src/types/- 全局 TS 类型定义

作用

存放项目全局的 TypeScript 接口、类型别名、枚举,统一管理类型,避免重复定义。

示例代码

ts

tsx 复制代码
// src/types/user.ts
// 枚举(TS 特性,限定取值范围)
export enum UserRole {
  Admin = 'admin',
  User = 'user',
  Guest = 'guest'
}

// 接口(定义对象结构)
export interface UserInfo {
  id: string;
  name: string;
  age?: number; // 可选属性
  role: UserRole;
  createTime: Date;
}

// 接口(API 请求/响应)
export interface UserListRequest {
  page: number;
  size: number;
  keyword?: string;
}

export interface UserListResponse {
  list: UserInfo[];
  total: number;
}

知识点关联

  • TS 接口 / 类型别名:React 组件 Props、API 数据、状态管理的类型约束核心;
  • 泛型 :后续在 Hooks/API 封装中会结合泛型(如 useRequest<T>());
  • 类型扩展 :通过 interface extends 实现类型复用(如 BaseResponse<T> 包裹所有接口响应)。

4. src/utils/- 工具函数

作用

存放通用工具函数(与业务无关),如时间格式化、防抖节流、本地存储、URL 解析等。

示例代码

ts

tsx 复制代码
// src/utils/format.ts
/** 时间格式化(工具函数示例) */
export const formatDate = (date: Date | string, format = 'YYYY-MM-DD'): string => {
  const targetDate = typeof date === 'string' ? new Date(date) : date;
  const year = targetDate.getFullYear();
  const month = String(targetDate.getMonth() + 1).padStart(2, '0');
  const day = String(targetDate.getDate()).padStart(2, '0');
  return format.replace('YYYY', year.toString()).replace('MM', month).replace('DD', day);
};

// src/utils/request.ts(接口请求封装,基于 Axios)
import axios from 'axios';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { BaseResponse } from '@/types/common';

// 创建 Axios 实例
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量
  timeout: 10000
});

// 请求拦截器(添加 Token)
request.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截器(统一处理响应)
request.interceptors.response.use(
  (response: AxiosResponse<BaseResponse<any>>) => {
    if (response.data.code !== 200) {
      alert(response.data.message);
      return Promise.reject(response.data);
    }
    return response.data.data;
  },
  (error) => {
    alert('请求失败:' + error.message);
    return Promise.reject(error);
  }
);

// 泛型封装,约束响应类型
export const requestGet = <T>(url: string, params?: any): Promise<T> => {
  return request.get(url, { params });
};

export const requestPost = <T>(url: string, data?: any): Promise<T> => {
  return request.post(url, data);
};

export default request;

知识点关联

  • Axios 拦截器:React 项目中接口请求的通用处理方案(统一加 Token、统一错误处理);
  • 环境变量 :Vite 中 import.meta.env 区分开发 / 生产环境(React 项目环境配置);
  • 工具函数纯函数化:无副作用,符合 React 函数式编程思想。

5. src/hooks/- 自定义 Hooks

作用

抽取组件复用逻辑(替代类组件的 mixin / 高阶组件),是 React 组件复用的核心方案。

示例代码

tsx

tsx 复制代码
// src/hooks/useUser.ts(业务自定义 Hook)
import { useState, useEffect } from 'react';
import { requestGet } from '@/utils/request';
import type { UserListResponse, UserInfo } from '@/types/user';

// 泛型 Hook:获取用户列表
export const useUserList = (page: number, size: number) => {
  // React Hooks 核心:状态 + 副作用
  const [list, setList] = useState<UserInfo[]>([]);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  // 副作用:页面/页码变化时请求数据
  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      setError(null);
      try {
        const data = await requestGet<UserListResponse>('/api/user/list', { page, size });
        setList(data.list);
      } catch (err) {
        setError((err as Error).message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [page, size]); // 依赖数组:React 副作用依赖追踪

  // 返回状态和方法,供组件使用
  return { list, loading, error };
};

// src/hooks/useDebounce.ts(通用自定义 Hook)
import { useState, useEffect } from 'react';

// 防抖 Hook
export const useDebounce = <T>(value: T, delay = 300): T => {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // 清理函数:React 副作用的清理(防止内存泄漏)
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
};

知识点关联

  • React Hooks 核心规则:只能在函数组件 / 自定义 Hook 中调用、不能条件调用;
  • useState/useEffect:状态管理 + 副作用处理(React 函数组件的核心);
  • 自定义 Hook 规范 :以 use 开头,复用逻辑,分离组件 UI 和业务逻辑;
  • 泛型 Hook:适配不同类型的数据,提升复用性(TS + React 最佳实践)。

6. src/components/- 通用组件

分类

  • common/:基础组件(与业务无关,如 Button、Input、Modal、Loading);
  • business/:业务通用组件(与业务强相关,如 UserCard、OrderTable)。

示例代码(基础组件)

tsx

tsx 复制代码
// src/components/common/Button/Button.tsx
import React from 'react';
import './Button.scss';

// TS 约束组件 Props
interface ButtonProps {
  type?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'middle' | 'large';
  onClick?: () => void;
  disabled?: boolean;
  children: React.ReactNode; // React 内置类型:子节点类型
  className?: string;
}

// 函数组件 + TS Props 约束
const Button: React.FC<ButtonProps> = ({
  type = 'primary',
  size = 'middle',
  onClick,
  disabled = false,
  children,
  className = ''
}) => {
  // 动态拼接类名
  const btnClass = `btn btn-${type} btn-${size} ${className}`;

  return (
    <button
      className={btnClass}
      onClick={onClick}
      disabled={disabled}
      type="button"
    >
      {children}
    </button>
  );
};

export default Button;

示例代码(业务组件)

tsx

tsx 复制代码
// src/components/business/UserCard/UserCard.tsx
import React from 'react';
import type { UserInfo } from '@/types/user';
import { formatDate } from '@/utils/format';
import Button from '@/components/common/Button/Button';
import './UserCard.scss';

// 业务组件 Props
interface UserCardProps {
  user: UserInfo;
  onEdit: (id: string) => void; // 回调函数:组件通信
}

const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>角色:{user.role}</p>
      <p>创建时间:{formatDate(user.createTime)}</p>
      <Button onClick={() => onEdit(user.id)} type="primary">
        编辑
      </Button>
    </div>
  );
};

export default UserCard;

知识点关联

  • React 组件类型React.FC(函数组件类型),内置 children 类型;
  • 组件 Props 约束:TS 接口定义 Props,明确入参类型(React + TS 核心);
  • 组件通信:父组件通过 Props 传递回调函数给子组件(单向数据流);
  • 组件样式隔离:SCSS 模块化、CSS Modules、Styled Components(React 样式方案);
  • React.ReactNode:兼容所有可渲染类型(文本、元素、数组、null 等)。

7. src/layouts/- 布局组件

作用

封装页面通用布局(如侧边栏 + 头部 + 内容区),所有页面复用布局结构。

示例代码

tsx

tsx 复制代码
// src/layouts/MainLayout/MainLayout.tsx
import React from 'react';
import { Outlet, useNavigate } from 'react-router-dom'; // React Router 知识点
import './MainLayout.scss';
import Button from '@/components/common/Button/Button';

const MainLayout: React.FC = () => {
  const navigate = useNavigate(); // React Router 导航 Hook

  // 退出登录逻辑
  const handleLogout = () => {
    localStorage.removeItem('token');
    navigate('/login');
  };

  return (
    <div className="main-layout">
      {/* 侧边栏 */}
      <aside className="sidebar">
        <ul className="menu">
          <li onClick={() => navigate('/')}>首页</li>
          <li onClick={() => navigate('/user/list')}>用户管理</li>
        </ul>
      </aside>
      {/* 头部 */}
      <header className="header">
        <h1>后台管理系统</h1>
        <Button onClick={handleLogout} type="danger">
          退出登录
        </Button>
      </header>
      {/* 内容区:Outlet 渲染路由匹配的子页面(React Router 嵌套路由) */}
      <main className="content">
        <Outlet />
      </main>
    </div>
  );
};

export default MainLayout;

知识点关联

  • React Router 嵌套路由Outlet 是嵌套路由的 "占位符",渲染子路由组件;
  • useNavigate:React Router 18 导航 Hook(替代旧版 useHistory);
  • 布局复用:通过路由配置关联布局,避免每个页面重复写布局代码。

8. src/router/- 路由配置

作用

集中管理路由规则(嵌套路由、路由守卫、懒加载),是 React SPA 页面跳转的核心。

示例代码

tsx

tsx 复制代码
// src/router/index.tsx
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import type { RouteObject } from 'react-router-dom';
import MainLayout from '@/layouts/MainLayout/MainLayout';
import NotFound from '@/pages/NotFound/NotFound';

// 路由懒加载:React 代码分割(提升首屏加载速度)
const Home = React.lazy(() => import('@/pages/Home/Home'));
const UserList = React.lazy(() => import('@/pages/User/List/List'));
const UserDetail = React.lazy(() => import('@/pages/User/Detail/Detail'));
const Login = React.lazy(() => import('@/pages/Login/Login'));

// 路由守卫:校验登录状态
const PrivateRoute = ({ children }: { children: React.ReactNode }) => {
  const hasToken = !!localStorage.getItem('token');
  return hasToken ? <>{children}</> : <Navigate to="/login" />;
};

// 路由配置数组(TS 约束:RouteObject)
const routes: RouteObject[] = [
  {
    path: '/login',
    element: <Login /> // 登录页(无需布局)
  },
  {
    path: '/',
    element: (
      <PrivateRoute>
        <MainLayout />
      </PrivateRoute>
    ),
    children: [
      // 嵌套路由:首页
      { path: '', element: <Navigate to="/home" /> }, // 重定向
      { path: 'home', element: <Home /> },
      // 用户模块嵌套路由
      { path: 'user/list', element: <UserList /> },
      { path: 'user/detail/:id', element: <UserDetail /> } // 动态路由参数
    ]
  },
  // 404 页面
  { path: '*', element: <NotFound /> }
];

// 创建路由器
const router = createBrowserRouter(routes);

// 路由组件(供 main.tsx 挂载)
const AppRouter: React.FC = () => {
  return (
    // React.Suspense:配合懒加载,加载中显示占位符
    <React.Suspense fallback={<div>加载中...</div>}>
      <RouterProvider router={router} />
    </React.Suspense>
  );
};

export default AppRouter;

知识点关联

  • React Router 6+ 核心createBrowserRouter/RouterProvider(新版路由 API);
  • 路由懒加载React.lazy + React.Suspense(代码分割,优化性能);
  • 嵌套路由children 配置 + Outlet 渲染(大型项目路由组织方式);
  • 路由守卫:自定义组件校验权限(登录状态、角色权限);
  • 动态路由参数/:id 匹配动态路径,通过 useParams 获取(页面传参);
  • 重定向Navigate 组件(替代旧版 Redirect)。

9. src/pages/- 页面组件

作用

路由对应的页面级组件,聚合 "布局 + 组件 + Hooks + 业务逻辑",是用户最终看到的页面。

示例代码(用户列表页)

tsx

tsx 复制代码
// src/pages/User/List/List.tsx
import React, { useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import UserCard from '@/components/business/UserCard/UserCard';
import Button from '@/components/common/Button/Button';
import { useUserList } from '@/hooks/useUser';
import { useDebounce } from '@/hooks/useDebounce';
import type { UserInfo } from '@/types/user';
import './List.scss';

const UserList: React.FC = () => {
  // 1. 路由参数(URL 查询参数)
  const [searchParams, setSearchParams] = useSearchParams();
  const initialPage = Number(searchParams.get('page')) || 1;

  // 2. 页面状态
  const [page, setPage] = useState<number>(initialPage);
  const [size, setSize] = useState<number>(10);
  const [keyword, setKeyword] = useState<string>('');
  const debouncedKeyword = useDebounce(keyword); // 防抖处理搜索词

  // 3. 调用自定义 Hook 获取数据
  const { list, loading, error } = useUserList(page, size);

  // 4. 业务方法:编辑用户
  const handleEdit = (id: string) => {
    // 跳转到详情页(带参数)
    window.location.href = `/user/detail/${id}`;
    // 或用 useNavigate:navigate(`/user/detail/${id}`);
  };

  // 5. 分页切换
  const handlePageChange = (newPage: number) => {
    setPage(newPage);
    // 更新 URL 查询参数
    setSearchParams({ page: newPage.toString(), size: size.toString() });
  };

  // 6. 渲染逻辑
  if (loading) return <div>加载中...</div>;
  if (error) return <div className="error">{error}</div>;

  return (
    <div className="user-list-page">
      <div className="search-bar">
        <input
          type="text"
          placeholder="搜索用户名"
          value={keyword}
          onChange={(e) => setKeyword(e.target.value)}
        />
        <Button type="primary">搜索</Button>
      </div>

      <div className="user-list">
        {list.length === 0 ? (
          <p>暂无用户数据</p>
        ) : (
          list.map((user) => (
            <UserCard key={user.id} user={user} onEdit={handleEdit} />
          ))
        )}
      </div>

      <div className="pagination">
        <Button
          disabled={page === 1}
          onClick={() => handlePageChange(page - 1)}
        >
          上一页
        </Button>
        <span>第 {page} 页</span>
        <Button onClick={() => handlePageChange(page + 1)}>下一页</Button>
      </div>
    </div>
  );
};

export default UserList;

知识点关联

  • useSearchParams:React Router 管理 URL 查询参数(替代 URLSearchParams);
  • 组件组合:页面组件聚合通用组件 / 业务组件,体现 React 组件化思想;
  • 状态管理:页面级状态用 useState,跨页面状态用全局状态管理(如下文 store);
  • 条件渲染:根据 loading/error 状态渲染不同内容(React 渲染逻辑);
  • key 属性:列表渲染必须加唯一 key(React 虚拟 DOM 优化)。

10. src/store/- 全局状态管理(以 Zustand 为例)

作用

管理跨组件 / 跨页面的全局状态(如用户信息、全局配置),替代 Redux(更轻量)。

示例代码

tsx

tsx 复制代码
// src/store/userStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { UserInfo } from '@/types/user';

// TS 约束 store 状态和方法
interface UserStore {
  userInfo: UserInfo | null;
  setUserInfo: (info: UserInfo | null) => void;
  clearUserInfo: () => void;
  isLogin: boolean; // 派生状态
}

// 创建 store(带持久化:localStorage)
const useUserStore = create<UserStore>()(
  persist(
    (set, get) => ({
      userInfo: null,
      // 设置用户信息
      setUserInfo: (info) => set({ userInfo: info }),
      // 清空用户信息
      clearUserInfo: () => set({ userInfo: null }),
      // 派生状态:根据 userInfo 判断是否登录
      get isLogin() {
        return !!get().userInfo;
      }
    }),
    {
      name: 'user-storage', // localStorage 键名
      partialize: (state) => ({ userInfo: state.userInfo }) // 只持久化 userInfo
    }
  )
);

export default useUserStore;

知识点关联

  • 全局状态管理核心:解决组件间状态共享问题(React 单向数据流的补充);
  • Zustand 优势:比 Redux 简洁,无需 Provider/Reducer,TS 友好;
  • 状态持久化:结合 localStorage 保存状态(页面刷新不丢失);
  • 派生状态:基于现有状态计算新状态(避免重复状态)。

11. src/main.tsx - 入口文件

作用

React 应用的入口,挂载根组件到 DOM 节点。

示例代码

tsx

tsx 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import AppRouter from '@/router';
import '@/assets/global.scss'; // 全局样式

// 挂载到 HTML 的 #root 节点(React 18 新 API)
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
  // React.StrictMode:严格模式,检测不规范用法(开发环境)
  <React.StrictMode>
    <AppRouter />
  </React.StrictMode>
);

知识点关联

  • React 18 挂载 APIcreateRoot 替代旧版 ReactDOM.render
  • StrictMode:开发环境下双重渲染组件,检测副作用 / 不规范代码;
  • 全局样式:入口文件导入全局样式,作用于整个应用。

12. 配置文件(tsconfig.json/vite.config.ts)

tsconfig.json(核心配置)

json

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    /* 路径别名 */
    "paths": {
      "@/*": ["./src/*"] // 别名@指向src,简化导入路径
    },
    /* 模块解析 */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx", // React JSX 编译模式
    /* 类型检查 */
    "strict": true, // 严格模式(TS 最佳实践)
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"], // 包含的 TS 文件
  "references": [{ "path": "./tsconfig.node.json" }]
}

vite.config.ts(核心配置)

ts

ts 复制代码
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  // 路径别名配置(和 tsconfig.json 对应)
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  // 开发服务器配置
  server: {
    port: 3000,
    open: true,
    proxy: {
      // 接口代理(解决跨域)
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

知识点关联

  • TS 路径别名:简化导入路径,避免相对路径层级混乱;
  • Vite 代理:解决开发环境接口跨域问题(前端跨域核心方案);
  • React 编译插件@vitejs/plugin-react 处理 JSX/TSX 编译。

三、核心知识点关联总结

文件夹 核心 React 知识点 核心 TS 知识点
components 组件化、Props、children、样式隔离 接口约束 Props、React.ReactNode
hooks Hooks 规则、useState/useEffect、自定义 Hook 泛型 Hook、类型推导
router 路由懒加载、嵌套路由、路由守卫、导航 RouteObject 类型、动态参数类型
store 全局状态管理、状态持久化 接口约束 store、派生状态
pages 组件组合、条件渲染、列表渲染(key) 页面状态类型、事件处理类型
api/utils 接口封装、副作用处理、工具函数 泛型请求函数、工具类型

最终总结

  1. 目录设计原则:按「功能 / 职责」划分(组件 / 页面 / Hooks / 路由 / 状态),符合 "高内聚、低耦合";
  2. React 核心落地 :组件化、Hooks、路由、状态管理是四大核心,对应目录中的 components/hooks/router/store
  3. TS 核心价值:全链路类型约束(Props / 接口 / 状态 / 工具函数),避免类型错误,提升可维护性;
  4. 性能优化点:路由懒加载、组件复用、key 属性、防抖节流(分散在各目录中);
  5. 工程化规范:路径别名、ESLint、环境变量、静态资源管理(配置文件 + 目录结构体现)。
相关推荐
yingyima1 小时前
Celery 分布式任务队列:我差点被这行代码坑死
前端
用户125758524361 小时前
XYGo Admin 即时通讯模块解析:基于 WebSocket 的企业级消息架构实践
前端
铁皮饭盒2 小时前
彩色命令行,Node21自带函数1行实现 ,Bun也兼容, 附Bun.color实现渐变色的代码
前端·后端
vim怎么退出2 小时前
Dive into React——Fiber架构
react.js·源码阅读
锋行天下2 小时前
关于websocket,真实场景踩坑经验
前端·后端
Asize2 小时前
重生之我在 Vibe Coding 时代当程序员:第十二课,Prompt 不是咒语,是可以沉淀的业务接口
前端·人工智能·python
布兰妮甜2 小时前
Vue 项目 `localhost:3000` 打不开?404 常见原因排查指南
前端·javascript·vue.js·vuecli·4040排查
森林的尽头是阳光2 小时前
前端使用postman快速造数据
前端·javascript·vue·postman·造数·本地测试
小猿备忘录3 小时前
Spring Security OAuth2 双Token机制精讲:原理、配置与常见坑点全解析
java·前端·spring·中间件