跳到主要内容

前端项目目录结构规范

本规范融合了业内最新的架构理念和最佳实践,旨在提供一个清晰、可扩展且易于维护的前端项目目录结构标准。

1. 命名规范

1.1 统一命名原则

推荐统一使用 kebab-case:

  • 所有文件夹:统一使用 kebab-case,如 auto-complete/user-management/api-client/
  • 所有文件:统一使用 kebab-case,如 auto-complete.tsxuser-profile.tsx
  • 组件类名:使用 PascalCase,如 AutoCompleteUserProfile
  • 目录命名规则
    • 文件容器目录:包含多个同类文件时使用复数,如 components/pages/hooks/
    • 功能性目录:表示功能领域或概念时使用单数,如 model/api/ui/lib/
    • 约定优先:遵循行业标准,而非严格的语法规则(如使用 lib/ 表示库目录而非 library/,虽然后者语义更完整)
  • 语义化:名称应清晰表达文件/文件夹的用途

1.2 特殊文件规则

1.2.1 index.ts(Barrel 文件)

  • 主要用途:重新导出模块,创建统一的导出入口
  • 最佳实践:保持简单,只做导出,避免副作用
// 推荐:简单的重新导出
export { Button } from "./button";
export { Input } from "./input";
export type { ButtonProps } from "./button";

// 谨慎:有条件的导出(可能影响 Tree-shaking)
export { Button } from "./button";
export { Input } from process.env.NODE_ENV === "development" ? "./input-dev" : "./input";

// 避免:包含业务逻辑
const theme = getTheme();
export { Button } from theme === "dark" ? "./button-dark" : "./button-light";

1.2.2 其他特殊文件

  • 测试文件
    • 推荐*.test.tsx*.spec.tsx(工具链兼容性更好)
    • 可选*-test.tsx*-spec.tsx(与命名规范更一致)
  • 类型文件*.types.tstypes.ts
  • 常量文件constants.ts*.constants.ts
  • 配置文件config.ts*.config.ts

1.3 组件类型命名约定

  • UI 组件:描述性名称(如 PrimaryButton, SearchInput, DataTable
  • 页面组件:组件类名以 Page 结尾(如 HomePage, ProfilePage),文件夹直接使用功能名称(如 home/, profile/
  • 布局组件:以 Layout 结尾(如 AppLayout, AuthLayout, DashboardLayout
  • 功能组件:描述功能(如 UserProfile, FileUpload, OrderSummary
  • Provider 组件:以 Provider 结尾(如 ThemeProvider, AuthProvider, DataProvider
  • 高阶组件:以 with 开头(如 withAuth, withLoading, withErrorBoundary
  • Hook 函数:以 use 开头(如 useAuth, useLocalStorage, useDebounce
  • 工具组件:描述用途(如 ErrorBoundary, LoadingSpinner, ProtectedRoute
  • 表单组件:以 Form 结尾或包含 Field(如 LoginForm, InputField, SelectField

1.4 代码格式化规范(Prettier)

为保证团队代码风格统一,建议全项目使用 Prettier 作为代码格式化工具。

  • 强制格式化:所有提交的代码必须经过 Prettier 格式化。

  • 自动化集成:推荐在项目根目录添加 .prettierrc 配置文件,并在 package.json 中配置 format 脚本:

    // package.json
    "scripts": {
    "format": "prettier --write ."
    }
  • 常用 Prettier 配置示例

    // .prettierrc
    {
    "singleQuote": true, // 使用单引号
    "semi": false, // 语句末尾不加分号
    "trailingComma": "all", // 多行时尽可能使用尾随逗号
    "printWidth": 100, // 每行最大长度 100
    "tabWidth": 2, // 缩进为 2 个空格
    "endOfLine": "lf" // 换行符统一为 LF
    }
  • 编辑器集成:建议在 VSCode 等编辑器中安装 Prettier 插件,启用"保存时自动格式化"。

  • CI 检查:可选,在 CI 流程中增加 Prettier 检查,防止未格式化代码合入主干。

统一的代码风格有助于提升团队协作效率,减少无意义的代码 review。

1.5 路径别名配置

在项目中启用路径别名,请参考以下配置:

// tsconfig.app.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";

export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});

2. 目录结构

2.1 设计原则

2.1.1 基本原则

  • 关注点分离:将逻辑、UI 和数据管理分开,提高可读性和可维护性
  • 模块化设计:将应用拆分为小的、可复用的组件,可独立开发和测试
  • DRY 原则:避免代码重复,将通用功能抽象为可复用模块
  • 功能优先:按功能而非技术类型组织代码,便于理解和维护

2.1.2 扩展性考虑

  • 按功能分组:相关的组件、hooks、工具函数放在一起
  • 就近原则:组件相关的样式、测试、资源文件放在同一目录
  • 删除友好:删除功能时,删除对应文件夹即可移除所有相关文件

2.2 项目结构划分方式

2.2.1 按功能划分

把一个功能相关联的文件都放在一起:

feed/
index.js
Feed.js
Feed.css
FeedStory.js
FeedStory.test.js
FeedAPI.js
profile/
index.js
Profile.js
ProfileHeader.js
ProfileHeader.css
ProfileAPI.js

按功能划分结构很清晰,一个功能的代码都在一起,要删除一个功能也不用到处去找相关的文件。但是,一个功能的定义因人而异,有时一个功能的定义非常模糊,拆分起来非常困难,比如要实现一个用户评价商品的功能,是应该属于用户管理还是商品管理?

2.2.2 按文件类型划分

将同一类型的文件放一起,比如所有的 component,所有的 api:

apis/
APIUtils.js
ProfileAPI.js
UserAPI.js
components/
Avatar.js
Feed.js
FeedStory.js
Profile.js
ProfileHeader.js

按类型放一起的好处在于,不用再纠结该放哪的问题,组件都放 components 就好了,api 都放 apis 下好了,当然这失去了按功能组织的优点,因为相关文件都散落在各处。

这两种项目结构并没有绝对的好坏之分。通常,随着项目扩大,一般都是混用这两种类型。之所以有结构,更多的是一种约定,开发者共同遵守这一约定即可。

2.2.3 静态资源(Assets)

静态资源的组织方式应遵循"全局集中 + 就近分散"的原则,根据资源的使用范围和业务相关性来决定存放位置。

2.2.3.1 全局静态资源

位置: src/shared/assets/

职责: 存放全局可复用、无业务耦合的静态资源

src/shared/assets/
├── images/ # 全局图片资源
│ ├── logo/ # 品牌 Logo
│ │ ├── logo.svg
│ │ ├── logo-dark.svg
│ │ └── favicon.ico
│ ├── icons/ # 通用图标集
│ │ ├── arrow-right.svg
│ │ ├── search.svg
│ │ └── user.svg
│ ├── illustrations/ # 通用插画
│ │ ├── empty-state.svg
│ │ ├── error-404.svg
│ │ └── loading.svg
│ └── placeholders/ # 占位图片
│ ├── avatar-default.png
│ └── image-placeholder.svg
├── fonts/ # 字体文件
│ ├── inter/
│ └── roboto/
├── videos/ # 视频文件
│ └── hero-background.mp4
└── index.ts # 资源路径统一导出

导出示例:

// src/shared/assets/index.ts
export const IMAGES = {
LOGO: "/images/logo/logo.svg",
LOGO_DARK: "/images/logo/logo-dark.svg",
EMPTY_STATE: "/images/illustrations/empty-state.svg",
DEFAULT_AVATAR: "/images/placeholders/avatar-default.png",
} as const;

export const ICONS = {
SEARCH: "/images/icons/search.svg",
USER: "/images/icons/user.svg",
ARROW_RIGHT: "/images/icons/arrow-right.svg",
} as const;
2.2.3.2 业务相关静态资源

位置: 各层内的 assets/ 目录

职责: 存放与特定业务功能强相关的静态资源

# Entity 层示例
entities/product/
├── assets/
│ └── images/
│ ├── product-placeholder.svg # 商品占位图
│ └── brand-badges/ # 品牌徽章
└── ui/
└── product-card.tsx # 使用上述资源

# Feature 层示例
features/user-auth/
├── assets/
│ └── images/
│ ├── login-illustration.svg # 登录页插画
│ └── social-icons/ # 社交登录图标
└── ui/
└── login-form.tsx # 使用上述资源

# Widget 层示例
widgets/header/
├── assets/
│ └── images/
│ └── navigation-icons/ # 导航专用图标
└── ui/
└── header.tsx # 使用上述资源

3. 推荐的项目结构划分

项目结构划分个人更倾向以 Feature-Sliced Design (FSD)为基础,src 目录大概分为以下六层:

src/
├── app/ # 应用层-第一层:全局配置和初始化
├── pages/ # 页面层-第二层:路由级页面组件
├── widgets/ # 组件层-第三层:复合业务组件
├── features/ # 功能层-第四层:功能模块
├── entities/ # 实体层-第五层:业务实体
└── shared/ # 共享层-第六层:可复用基础设施

3.1 架构层规则

3.1.1. 依赖方向

上层可以使用下层,下层不能使用上层。

  • 分层依赖关系图

    ┌─────────────┐
    │ app/ │ ← 最上层:应用入口,可以使用所有下层
    └─────────────┘

    ┌─────────────┐
    │ pages/ │ ← 第二层:页面层,可以使用下面4层
    └─────────────┘

    ┌─────────────┐
    │ widgets/ │ ← 第三层:组件层,可以使用下面3层
    └─────────────┘

    ┌─────────────┐
    │ features/ │ ← 第四层:功能层,可以使用下面2层
    └─────────────┘

    ┌─────────────┐
    │ entities/ │ ← 第五层:实体层,只能使用最下层
    └─────────────┘

    ┌─────────────┐
    │ shared/ │ ← 最下层:共享层,不依赖任何上层
    └─────────────┘
  • 依赖示例说明

    // 正确:pages 层使用 widgets 层
    // pages/home/index.tsx
    import { ProductList } from "@/widgets/product-list";
    import { Header } from "@/widgets/header";

    // 正确:features 层使用 entities 层和 shared 层
    // features/shopping-cart/ui/cart-item.tsx
    import { Product } from "@/entities/product";
    import { Button } from "@/shared/ui";

    // 正确:entities 层使用 shared 层
    // entities/user/api/user-api.ts
    import { httpClient } from "@/shared/api";

    // 错误:下层不能使用上层
    // shared/ui/button.tsx
    import { useAuth } from "@/features/auth"; // 错误!shared 不能使用 features

3.1.2 同层隔离

同一层的模块不能直接相互依赖。例如:

// 错误:同层不能直接相互依赖
// features/auth/model/auth-store.ts
import { cartStore } from "@/features/shopping-cart"; // 错误!同层直接依赖

// 正确:同层通过共享层通信
// features/auth/model/auth-store.ts
import { eventBus } from "@/shared/lib/event-bus";
eventBus.emit("user-logged-in", userData);

// features/shopping-cart/model/cart-store.ts
import { eventBus } from "@/shared/lib/event-bus";
eventBus.on("user-logged-in", handleUserLogin);

3.1.3 Public API

Features 层、Entities 层和 Shared 层的模块需要在每个切片下创建 index.ts 文件来暴露 Public API,而不是在这些层直接创建 index.ts:

  • Features 层

    // features/product-list/index.ts
    export { ProductList } from "./ui";
    export { useProducts } from "./model";
  • Entities 层

    // entities/user/index.ts
    export type { User } from "./model";
    export { UserCard } from "./ui";
  • Shared 层

    // shared/ui/index.ts
    export { Button } from "./button";
    export { Input } from "./input";

3.2 层详解

3.2.1 app/ - 应用层(最上层)

App 层负责处理所有应用级事务,包括技术层面(如上下文提供者 providers)和业务层面(如埋点、全局错误处理、全局权限守卫、统计 analytics)的全局配置。

以下是一个基础结构(可以根据项目需求动态添加或移除目录)示例:

app/
├── providers/ # 全局提供者
│ ├── theme-provider.tsx # 主题提供者
│ ├── auth-provider.tsx # 认证提供者
│ ├── router-provider.tsx # 路由提供者
│ └── index.ts # 提供者统一导出
├── styles/ # 全局样式
│ ├── globals.css # 全局基础样式
│ ├── variables.css # CSS变量定义
│ ├── reset.css # 样式重置
│ └── themes/ # 主题相关样式
│ ├── light.css
│ ├── dark.css
│ └── index.ts
├── store/ # 全局状态管理
│ ├── middleware.ts # 中间件配置
│ ├── root-reducer.ts # 根reducer
│ └── index.ts # Store入口
├── routing/ # 路由相关
│ ├── routes.tsx # 路由组件
│ ├── guards.tsx # 路由守卫
│ ├── lazy-imports.ts # 懒加载导入
│ └── index.ts
├── app.tsx # 应用层统一导出
└── index.ts # 应用入口组件

app 层示例:

// app/index.ts - 只需要导出 App
export { App } from "./app";

// app/app.tsx - 应用入口组件
import { ThemeProvider } from "./providers/theme-provider";
import { AuthProvider } from "./providers/auth-provider";
import { RouterProvider } from "./providers/router-provider";
import { AppRoutes } from "./routing";
import "./styles/globals.css";

export function App() {
return (
<ThemeProvider>
<AuthProvider>
<RouterProvider>
<AppRoutes />
</RouterProvider>
</AuthProvider>
</ThemeProvider>
);
}

3.2.2 pages/ - 页面层

应用的页面级组件,对应路由,主要负责页面级组件的组装和简单的状态协调:

  • 职责清晰: 只负责组装和编排各个功能模块
  • 保持简单: 不包含复杂的业务逻辑,复杂逻辑下沉到 Features 层
  • 状态管理: 只处理页面级的简单状态,复杂状态管理在 Features 层实现
  • 路由处理: 负责处理页面级的路由参数和跳转
pages/
├── home/
├── product/
│ ├── index.ts # 导出product.tsx
│ ├── product.tsx # 实际的页面组件
│ ├── product.module.css # 页面样式
│ └── ui/ # 页面专用组件
│ ├── product-gallery.tsx # 商品图片画廊
│ ├── product-info.tsx # 商品信息区域
│ └── related-products.tsx # 相关商品推荐
└── not-found/

Pages 层示例:

// pages/product/product.tsx
import { ProductDetail } from "@/features/product-detail";
import { ProductReviews } from "@/features/product-reviews";

export function ProductPage() {
return (
<div className="product-page">
<ProductDetail />
<ProductReviews />
</div>
);
}

3.2.3 widgets/ - 组件层

widgets 层用于存放大型、自给自足的 UI 模块。这些模块通常是复合业务组件,可以组合多个 features 和 entities,形成完整的功能单元。例如页头、侧边栏、商品列表等跨页面复用的大型 UI 组件。

特点:

  • 独立性强(通常包含完整的 UI + 数据获取 + 加载状态 + 错误边界)。
  • 常用于跨页面复用的大块 UI。
  • 或者某个页面里有多个“大块 UI”,可以拆成 Widgets。
widgets/
├── header/ # 页头组件
├── product-search-list/ # 商品搜索
└── notification-center/ # 通知中心 Widget
  • 示例目录骨架(推荐):

    <widget>/
    ├── index.ts # Public API(仅导出)
    ├── ui/ # 组件组合与视图
    ├── model/ # 状态与派生逻辑(可选)
    ├── api/ # 数据获取(可选)
    ├── lib/ # 内部工具(可选)
    └── __tests__/ # 测试(可选)

    提示:Widget 通过自身 Public API 向外暴露必要能力,内部实现(各段)不被外部直接引用。

  • 最小示例(仅示意):

    // widgets/product-search-list/index.ts
    export { ProductSearchList } from "./ui/product-search-list";
    export * as ProductSearchListModel from "./model";

    // widgets/product-search-list/ui/product-search-list.tsx
    import { useEffect } from "react";
    import { useProductSearchList } from "../model/use-product-search-list";

    export function ProductSearchList() {
    const { items, load } = useProductSearchList();
    useEffect(() => {
    load();
    }, [load]);
    return (
    <ul>
    {items.map((p) => (
    <li key={p.id}>{p.name}</li>
    ))}
    </ul>
    );
    }

    // widgets/product-search-list/model/use-product-search-list.ts
    import { useCallback, useState } from "react";
    import { fetchProducts } from "../api/fetch-products";

    export function useProductSearchList() {
    const [items, setItems] = useState<Array<{ id: string; name: string }>>([]);
    const load = useCallback(async () => {
    const data = await fetchProducts();
    setItems(data);
    }, []);
    return { items, load };
    }

    // widgets/product-search-list/api/fetch-products.ts
    export async function fetchProducts() {
    // 仅示意
    return Promise.resolve([{ id: "1", name: "Demo Product" }]);
    }

3.2.4 features/ - 功能层

3.2.4.1 Features 层的职责
  • 业务逻辑封装 :包含完整的功能实现

  • 数据管理 :负责自己的数据获取、状态管理

  • 自包含性 :feature 应该是独立、可复用的

  • 示例目录骨架(推荐):

    <feature>/
    ├── index.ts # Public API(仅导出)
    ├── ui/ # 视图组件(Feature 级复合组件)
    ├── model/ # 状态、selectors、副作用
    ├── api/ # 与该 Feature 相关的 API 客户端
    ├── lib/ # 仅供该 Feature 内部复用的工具
    ├── assets/ # 仅该 Feature 内部使用的资源(图片等)
    └── __tests__/ # 单测或集成测试

提示:外部模块仅依赖该 Feature 的 Public API,禁止直接引用内部实现文件(各段)。

  • 最小示例(仅示意):

    // features/product-search/index.ts
    export { SearchPanel } from "./ui/search-panel";
    export { useProductSearch } from "./model/use-product-search";

    // features/product-search/ui/search-panel.tsx
    import { useProductSearch } from "../model/use-product-search";
    export function SearchPanel() {
    const { query, results, setQuery, search } = useProductSearch();
    return (
    <div>
    <input value={query} onChange={(e) => setQuery(e.target.value)} />
    <button onClick={search}>Search</button>
    <ul>
    {results.map((r) => (
    <li key={r.id}>{r.name}</li>
    ))}
    </ul>
    </div>
    );
    }

    // features/product-search/model/use-product-search.ts
    import { useCallback, useState } from "react";
    import { searchProducts } from "../api/search";

    export function useProductSearch() {
    const [query, setQuery] = useState("");
    const [results, setResults] = useState<Array<{ id: string; name: string }>>(
    []
    );
    const search = useCallback(async () => {
    const data = await searchProducts(query);
    setResults(data);
    }, [query]);
    return { query, results, setQuery, search };
    }

    // features/product-search/api/search.ts
    export async function searchProducts(q: string) {
    // 仅示意:真实项目请接入 shared/api 或具体客户端
    if (!q) return [];
    return Promise.resolve([{ id: "1", name: `Result for ${q}` }]);
    }

    // features/product-search/ui/logo-usage.tsx(可选示例:本地 assets 仅供本 Feature 使用)
    import Logo from "../assets/logo.svg";
    export function LogoUsage() {
    return <img src={Logo} alt="logo" />;
    }
3.2.4.2 Feature 的定义原则
  • 什么是 Feature?

    • 用户价值导向:能为用户提供具体价值的功能单元
    • 业务完整性:包含完整的业务流程,从用户交互到数据处理
    • 独立性:可以独立开发、测试、部署的功能模块
  • Feature 划分标准:

    按用户故事划分

    好的划分:
    - auth/ # "用户可以登录/注册"
    - product-search/ # "用户可以搜索商品"
    - shopping-cart/ # "用户可以管理购物车"
    - order-checkout/ # "用户可以下单结账"

    错误划分:
    - forms/ # 太技术化,不是用户功能
    - buttons/ # 太细粒度,不是完整功能
    - api-calls/ # 技术实现,不是用户价值

    按业务边界划分

    电商项目示例:
    features/
    ├── user-auth/ # 用户认证相关
    ├── product-catalog/ # 商品浏览相关
    ├── shopping-cart/ # 购物车相关
    ├── payment-processing/ # 支付处理相关
    ├── user-profile/ # 用户资料相关
    └── review-rating/ # 评价评分相关
  • Feature 划分的实际例子:购物平台

    features/
    ├── user-auth/ # 用户登录注册
    ├── product-search/ # 商品搜索
    ├── shopping-cart/ # 购物车
    └── order-checkout/ # 订单结账
  • 具体实现示例:

    // features/product-search/
    export const ProductSearch = {
    SearchBar: () => <SearchInput />,
    SearchResults: () => <ProductList />,

    useProductSearch: () => {
    /* 搜索逻辑 */
    },
    searchProducts: (query: string) => {
    /* 搜索API */
    },
    };

    // features/shopping-cart/
    export const ShoppingCart = {
    CartIcon: () => <CartButton />,
    CartDrawer: () => <CartSidebar />,

    useCart: () => {
    /* 购物车状态 */
    },
    addToCart: (productId: string) => {
    /* 添加商品 */
    },
    };
  • 页面中使用:

    // pages/home/home.tsx
    import { ProductSearch } from "@/features/product-search";
    import { ShoppingCart } from "@/features/shopping-cart";

    export function HomePage() {
    return (
    <div>
    <header>
    <ProductSearch.SearchBar />
    <ShoppingCart.CartIcon />
    </header>
    <main>
    <ProductSearch.SearchResults />
    </main>
    </div>
    );
    }
3.2.4.3 购物平台 Feature 划分标准
  • 用户价值测试

    正确示例:
    - "用户可以搜索商品" → product-search
    - "用户可以管理购物车" → shopping-cart
    - "用户可以登录注册" → user-auth
    - "用户可以下单结账" → order-checkout

    错误示例:
    - "用户可以使用搜索框" → 太技术化
    - "用户可以点击按钮" → 不是业务价值
    - "用户可以看到商品" → 只是展示
  • 功能完整性测试 - 每个 Feature 应包含完整的用户交互流程:

    product-search/
    ├── 搜索输入 → 搜索执行 → 结果展示 → 筛选排序
    shopping-cart/
    ├── 添加商品 → 查看购物车 → 修改数量 → 删除商品
    user-auth/
    ├── 注册 → 登录 → 密码重置 → 退出登录
  • 业务边界测试

    边界清晰:
    - product-search: 只负责商品搜索相关
    - shopping-cart: 只负责购物车管理
    - order-checkout: 只负责订单结账流程

    边界模糊:
    - product-management: 太宽泛,包含搜索、详情、评价等
    - user-operations: 太宽泛,包含登录、资料、订单等
  • 团队协作测试

    可并行开发:
    - 搜索团队 → product-search
    - 购物车团队 → shopping-cart
    - 支付团队 → order-checkout
    - 用户团队 → user-auth

    每个团队可以独立开发、测试、部署自己的 Feature
  • 数据依赖测试

    依赖关系清晰:
    product-search → 依赖 entities/product
    shopping-cart → 依赖 entities/product, entities/user
    order-checkout → 依赖 shopping-cart, entities/order

    循环依赖:
    Feature A 依赖 Feature B,同时 Feature B 依赖 Feature A

3.2.5 entities/ - 实体层

业务领域中的核心数据对象,代表现实世界中具有独立身份的业务概念。

3.2.5.1 什么是 Entities?

简单理解:Entities 就是你的应用中的核心数据对象。

比如在购物网站中:

  • 用户(User) - 包含姓名、邮箱、头像等信息
  • 商品(Product) - 包含标题、价格、图片等信息
  • 订单(Order) - 包含商品列表、总价、状态等信息

举个例子:

// 不好的做法:在每个功能中重复定义用户数据
// features/user-profile/user-profile.tsx
interface User {
id: string;
name: string;
email: string;
}

// features/shopping-cart/cart.tsx
interface User {
// 重复定义!
id: string;
name: string;
email: string;
}

// 好的做法:在 entities 中统一定义
// entities/user/model/types.ts
export interface User {
id: string;
name: string;
email: string;
}

// features/user-profile/user-profile.tsx
import { User } from "@/entities/user"; // 直接使用

// features/shopping-cart/cart.tsx
import { User } from "@/entities/user"; // 复用相同定义
3.2.5.2 目录结构
entities/
├── user/ # 用户实体
├── product/ # 商品实体
├── order/ # 订单实体
└── category/ # 商品分类实体
  • 示例目录骨架(推荐):

    <entity>/
    ├── index.ts # Public API(仅导出)
    ├── ui/ # 实体展示组件(可选)
    ├── model/ # 实体数据模型、工具函数
    ├── api/ # 实体相关的 API 请求函数(可选)
    ├── lib/ # 仅供该实体内部复用的工具(可选)
    └── __tests__/ # 测试(可选)

    提示:Entity 通过自身 Public API 向外暴露必要能力,内部实现(各段)不被外部直接引用。

  • 具体示例:用户实体

    // entities/user/model/types.ts
    export interface User {
    id: string;
    name: string;
    email: string;
    avatar?: string;
    memberLevel: "NORMAL" | "VIP" | "PREMIUM";
    createdAt: Date;
    updatedAt: Date;
    }

    // entities/user/model/utils.ts
    export const UserUtils = {
    getFullName: (user: User) => user.name,
    isVip: (user: User) => user.memberLevel !== "NORMAL",
    getAvatarUrl: (user: User) => user.avatar || "/default-avatar.png",
    };

    // entities/user/api/api.ts
    export const UserAPI = {
    getUser: (id: string): Promise<User> =>
    fetch(`/api/users/${id}`).then((res) => res.json()),

    updateUser: (id: string, data: Partial<User>): Promise<User> =>
    fetch(`/api/users/${id}`, {
    method: "PUT",
    body: JSON.stringify(data),
    }).then((res) => res.json()),
    };

    // entities/user/ui/avatar.tsx
    export function UserAvatar({ user, size = "medium" }: UserAvatarProps) {
    return (
    <img
    src={UserUtils.getAvatarUrl(user)}
    alt={user.name}
    className={`avatar avatar-${size} ${
    UserUtils.isVip(user) ? "vip" : ""
    }`}
    />
    );
    }

    // entities/user/index.ts
    export { User } from "./model/types";
    export { UserUtils } from "./model/utils";
    export { UserAPI } from "./api/api";
    export { UserAvatar } from "./ui/avatar";

3.2.6 shared/ - 共享层

可复用的基础设施,通常(不绝对)不包含业务逻辑:

shared/
├── assets/ # 全局可复用的静态资源(图片、图标、字体、视频)
│ ├── images/
│ ├── icons/
│ ├── fonts/
│ ├── videos/
│ └── index.ts # 统一导出
├── ui/ # 基础 UI 组件库
│ ├── button/
│ ├── input/
│ ├── modal/
│ └── index.ts
├── lib/ # 通用工具库(utils、hooks、validation、mappers...)
│ ├── utils/
│ ├── hooks/
│ ├── validation/
│ ├── mappers/
│ └── index.ts
├── api/ # 基础 API 配置与客户端
│ ├── base.ts
│ ├── types.ts
│ └── index.ts
├── config/ # 基础配置(环境、常量、路由)
│ ├── env.ts
│ ├── constants.ts
│ ├── routes.ts
│ └── index.ts
└── i18n # 多语言
├── i18n.ts
├── locales/
└── index.ts

4. FAQ

Q: 同层模块之间如何通信?

  • A: 通过上层编排(pages/widgets)传递 props,或抽象出 shared/lib 事件总线/服务进行解耦,避免直接相互导入。

Q: 自定义 Hook 放哪?

  • A: 跨业务通用 Hook 放 shared/lib/hooks;仅在某个 feature 内使用的 Hook 放该 feature 的 model/

Q: 什么时候该创建 Widget?

  • A: 当一个 UI 模块体量较大、需跨页面复用且通常自带数据加载/空态/错误处理时,优先抽为 widgets/

Q: 实体类型定义放哪里?

  • A: 放在 entities/*/model,由各层通过 entities/*/index.ts 统一导出与复用。

Q: 路由与守卫放哪?

  • A: 放 app/routing(如 routes.tsxguards.tsx),由 app 统一装配。

5. 参考资源

5.1 业内标准文档

5.2 知名项目参考

5.3 社区资源

5.4 工具和模板

本规范基于业内最佳实践制定,会根据社区发展和团队反馈持续更新。


附录 A. 术语约定

  • 层(Layer):如 app / shared / entities / features / widgets / pages。
  • 切片(Slice):在 entities、features、widgets、pages 层内按业务或领域划分的子目录,如 entities/user、features/product-search、widgets/header、pages/home。
  • 段(Segment):切片内的功能分段,如 ui / model / api / lib / assets / config。
  • Public API:对外暴露的公共接口(通常由 index.ts 统一导出)。
  • Barrel 文件:只做导出的聚合文件(通常为 index.ts),避免包含副作用。