Files
test-repo/docs/zh-cn/appendix/3-browser-and-frontend/frontend-project-architecture.md
T
sanbuphy df51f84ab5 docs: 重构 README 附录展示 & 新增多个附录交互组件
README 更新:
- 移除顶部 header.png 横幅图片
- 新增「附录知识库」板块,以 3×3 网格展示 9 大知识领域精选内容
- 附录链接指向部署版网站 (datawhalechina.github.io)
- 阶段表格新增「附录」行,突出 80+ 交互式专题
- 章节标题「新手入门 & PM」简化为「零基础入门」
- News 新增 2026-02-25 附录知识库更新条目

新增交互组件:
- 异步任务队列 (async-task-queues) 演示组件
- 文件存储 (file-storage) 演示组件
- 项目架构 (project-architecture) 演示组件
- 限流与背压 (rate-limiting) 演示组件
- 搜索引擎 (search-engines) 演示组件
- 计算机基础: AppLaunch/BiosUefi/OSBoot 等启动流程演示组件

新增附录文档:
- 前端项目架构 (frontend-project-architecture.md)
- 后端项目架构 (backend-project-architecture.md)

内容优化:
- 算法思维、数据结构、编程语言、调试艺术等多篇附录内容更新
- HTML/CSS 布局、请求旅程等前后端文档完善
- 附录索引页 (index.md) 同步更新
2026-02-25 12:22:49 +08:00

18 KiB
Raw Blame History

前端项目架构设计

::: tip 🎯 核心问题 文件越放越乱,代码越写越难找,如何设计一个清晰、可维护的前端项目结构? 这就像问:你是把所有衣服都扔进一个箱子,还是按季节、类型、颜色分类整理?好的项目架构能让团队协作更高效,让代码维护更轻松。 :::


1. 为什么要关注项目架构?

1.1 从小项目到大项目的演变

很多初学者刚开始写前端时,项目结构非常简单:

my-project/
├── index.html
├── style.css
└── app.js

三个文件搞定一切,简单直接。但随着项目增长,问题开始出现:

  • 页面多了page1.html, page2.html... 文件散落在根目录
  • 组件多了:按钮、弹窗、表单各自为政,复用困难
  • 工具函数多了:到处复制粘贴,改一个地方要改十处
  • 样式冲突了:全局 CSS 互相覆盖,调试困难

问题的本质:没有"章法",文件随意存放,就像把春夏秋冬的衣服都扔进一个箱子。

1.2 好的架构像整理好的衣柜

想象一个整理好的衣柜:

区域 存放物品 特点
挂衣区 外套、衬衫 常穿,方便取用
抽屉区 内衣、袜子 分类摆放,整齐
隔板区 毛衣、裤子 叠放,节省空间
顶层区 换季衣物 不常用,收纳起来

好的项目架构就是把代码也这样组织:每一类文件有自己的"位置",团队成员都知道该去哪找、该往哪放。

::: tip 💡 通俗比喻:餐厅后厨的组织 把前端项目想象成一家餐厅的后厨:

  • src/pages/(页面区) = 出餐口:每个订单对应一个成品菜
  • src/components/(组件区) = 备料台:切好的蔬菜、调好的酱料,随时可用
  • src/utils/(工具区) = 工具柜:刀、勺、温度计等通用工具
  • src/assets/(食材区) = 冷藏库:图片、字体、样式等原材料
  • src/services/(服务层) = 传菜窗口:与外部(服务员/后端)交互

关键点:每个区域职责明确,不会混乱。你不会在冷藏库里切菜,也不会把刀具扔进汤锅。 :::


2. 经典目录结构解析

2.1 标准目录结构(以 Vue/React 为例)

一个中大型前端项目的典型结构如下:

my-frontend-project/
├── public/                     # 静态资源(不经过构建)
│   ├── favicon.ico
│   ├── index.html
│   └── robots.txt
├── src/
│   ├── assets/                 # 项目资源(会被构建工具处理)
│   │   ├── images/
│   │   ├── fonts/
│   │   └── styles/
│   │       ├── variables.scss  # 变量定义
│   │       ├── mixins.scss     # 混入样式
│   │       └── global.css      # 全局样式
│   ├── components/             # 通用组件
│   │   ├── common/             # 全局通用组件
│   │   │   ├── Button/
│   │   │   │   ├── index.vue
│   │   │   │   ├── Button.scss
│   │   │   │   └── Button.test.js
│   │   │   ├── Modal/
│   │   │   └── Loading/
│   │   └── business/           # 业务组件
│   │       ├── UserCard/
│   │       └── ProductList/
│   ├── views/ 或 pages/        # 页面组件
│   │   ├── Home/
│   │   ├── About/
│   │   └── User/
│   │       ├── Profile/
│   │       └── Settings/
│   ├── router/ 或 navigation/  # 路由配置
│   │   └── index.js
│   ├── stores/ 或 state/       # 状态管理
│   │   ├── user.js
│   │   └── app.js
│   ├── services/ 或 api/       # API 服务
│   │   ├── user.js
│   │   └── product.js
│   ├── utils/ 或 helpers/      # 工具函数
│   │   ├── request.js          # 请求封装
│   │   ├── storage.js          # 本地存储
│   │   └── format.js           # 格式化工具
│   ├── hooks/ 或 composables/  # 组合式函数
│   │   ├── useAuth.js
│   │   └── useLoading.js
│   ├── directives/             # 自定义指令
│   ├── plugins/                # 插件配置
│   ├── constants/              # 常量定义
│   ├── types/ 或 @types/       # TypeScript 类型
│   └── App.vue 或 App.jsx      # 根组件
│   └── main.js 或 main.ts      # 入口文件
├── tests/                      # 测试文件
│   ├── unit/
│   └── e2e/
├── .env                        # 环境变量
├── .env.development
├── .env.production
├── vite.config.js              # 构建配置
├── package.json
└── README.md

::: tip 📊 从图解中你能看到什么? 分层逻辑

  • public/ vs src/assets/:前者直接复制到输出目录,后者会被构建工具处理(压缩、转译、添加哈希值)
  • components/ vs views/:组件是"零件",页面是"成品"。一个页面由多个组件组装而成
  • services/ 独立出来:把 API 调用集中管理,方便统一处理错误、加载状态、请求拦截

依赖方向

views/pages → components → utils/hooks
     ↓
services → stores

上层可以调用下层,但下层不应该依赖上层。 :::

2.2 按功能组织 vs 按类型组织

项目结构有两种主流的组织方式:

方式一:按类型组织(Type-based)

src/
├── components/
│   ├── Button.vue
│   ├── Modal.vue
│   └── Card.vue
├── views/
│   ├── Home.vue
│   ├── User.vue
│   └── Product.vue
├── stores/
│   ├── user.js
│   └── product.js
└── services/
    ├── user.js
    └── product.js

优点

  • 结构清晰,同类文件在一起
  • 适合小型项目,一目了然

缺点

  • 修改一个功能要跨多个目录
  • 大型项目中文件过多,难以定位

方式二:按功能组织(Feature-based

src/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   │   ├── LoginForm.vue
│   │   │   └── RegisterForm.vue
│   │   ├── stores/
│   │   │   └── authStore.js
│   │   ├── services/
│   │   │   └── authApi.js
│   │   ├── hooks/
│   │   │   └── useAuth.js
│   │   └── index.js            # 统一导出
│   ├── user/
│   │   ├── components/
│   │   ├── stores/
│   │   └── services/
│   └── product/
│       ├── components/
│       ├── stores/
│       └── services/
├── shared/                     # 共享资源
│   ├── components/
│   ├── utils/
│   └── styles/
└── App.vue

优点

  • 高内聚,修改一个功能在一个目录完成
  • 便于团队协作,不同人负责不同 feature
  • 易于删除或重构,不会散落各处

缺点

  • 初期设计需要考虑 feature 划分
  • 共享组件需要额外考虑

::: tip 💡 如何选择?

项目规模 推荐方式 原因
小型项目(< 10 个页面) 按类型组织 简单直接,快速上手
中大型项目(> 20 个页面) 按功能组织 便于维护,团队协作
微前端/大型应用 按功能 + 模块拆分 独立部署,团队自治

实际建议:很多项目采用"混合模式"——整体按功能组织,内部按类型细分。 :::


3. 各目录的职责与最佳实践

3.1 components/ 组件目录

组件是前端项目的核心,良好的组件设计能大幅提升开发效率。

组件分类

components/
├── common/                 # 通用组件(跨项目可复用)
│   ├── Button/
│   ├── Input/
│   ├── Modal/
│   └── Loading/
├── business/               # 业务组件(项目特定)
│   ├── UserCard/
│   ├── ProductItem/
│   └── OrderTable/
└── layout/                 # 布局组件
    ├── Header/
    ├── Sidebar/
    └── Footer/

单文件组件结构

每个组件建议包含以下文件:

Button/
├── index.vue               # 主组件(或 .tsx/.jsx
├── Button.scss             # 样式(可选 CSS Modules
├── Button.test.js          # 单元测试
├── Button.stories.js       # Storybook 文档(可选)
├── types.ts                # 类型定义(TS 项目)
└── index.ts                # 统一导出

::: details 📝 组件代码示例

<!-- Button/index.vue -->
<template>
  <button
    :class="['btn', `btn--${type}`, { 'btn--disabled': disabled }]"
    :disabled="disabled"
    @click="handleClick"
  >
    <Loading v-if="loading" size="small" />
    <slot />
  </button>
</template>

<script setup>
import { Loading } from '../Loading'

defineProps({
  type: { type: String, default: 'primary' },
  disabled: Boolean,
  loading: Boolean
})

const emit = defineEmits(['click'])
const handleClick = () => emit('click')
</script>

<style scoped lang="scss">
.btn {
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  
  &--primary {
    background: var(--primary-color);
    color: white;
  }
  
  &--disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
}
</style>

:::

3.2 views/pages/ 页面目录

页面是用户看到的"成品",通常对应路由。

views/
├── Home/                   # 首页
│   ├── index.vue
│   ├── components/         # 页面私有组件
│   │   ├── HeroSection.vue
│   │   └── FeatureList.vue
│   └── hooks/              # 页面私有 hooks
│       └── useHomeData.js
├── User/
│   ├── Profile/
│   ├── Settings/
│   └── OrderHistory/
└── Product/
    ├── List/
    └── Detail/

最佳实践

  • 页面组件保持"薄",逻辑下沉到 hooks 或 services
  • 页面私有组件放在页面目录下,避免污染全局
  • 复杂页面可以进一步拆分子目录

3.3 services/api/ 服务层

集中管理所有 API 调用,统一处理请求/响应拦截。

services/
├── request.js              # 请求实例配置(axios/fetch 封装)
├── user.js                 # 用户相关 API
├── product.js              # 商品相关 API
├── order.js                # 订单相关 API
└── index.js                # 统一导出

::: details 📝 服务层代码示例

// services/request.js
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'

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

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    const authStore = useAuthStore()
    if (authStore.token) {
      config.headers.Authorization = `Bearer ${authStore.token}`
    }
    return config
  }
)

// 响应拦截器
request.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response?.status === 401) {
      // 统一处理登录过期
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

export default request
// services/user.js
import request from './request'

export const userApi = {
  login: (data) => request.post('/auth/login', data),
  register: (data) => request.post('/auth/register', data),
  getProfile: () => request.get('/user/profile'),
  updateProfile: (data) => request.put('/user/profile', data)
}

:::

3.4 stores/ 状态管理

stores/
├── index.js                # store 入口
├── auth.js                 # 认证状态
├── user.js                 # 用户信息
├── app.js                  # 应用级状态(主题、语言等)
└── cart.js                 # 购物车状态

建议

  • 按功能拆分 store,避免单个文件过大
  • 区分全局状态和局部状态,不要什么都放全局
  • 使用组合式 APIPinia/Vuex 4+)更灵活

3.5 utils/ 工具函数

utils/
├── format.js               # 格式化(日期、金额等)
├── storage.js              # 本地存储封装
├── validate.js             # 表单验证
├── dom.js                  # DOM 操作
├── date.js                 # 日期处理
└── index.js                # 统一导出

原则

  • 纯函数优先,便于测试
  • 单一职责,一个函数只做一件事
  • 添加 JSDoc 注释,说明参数和返回值

::: details 📝 工具函数示例

// utils/storage.js
const STORAGE_PREFIX = 'myapp_'

export const storage = {
  get(key) {
    const value = localStorage.getItem(STORAGE_PREFIX + key)
    try {
      return JSON.parse(value)
    } catch {
      return value
    }
  },
  
  set(key, value) {
    localStorage.setItem(
      STORAGE_PREFIX + key,
      typeof value === 'string' ? value : JSON.stringify(value)
    )
  },
  
  remove(key) {
    localStorage.removeItem(STORAGE_PREFIX + key)
  }
}

:::

3.6 hooks/composables/ 组合式函数

hooks/
├── useAuth.js              # 认证逻辑
├── useLoading.js           # 加载状态
├── usePagination.js        # 分页逻辑
├── useForm.js              # 表单处理
└── useWebsocket.js         # WebSocket

::: details 📝 Hook 示例

// hooks/useLoading.js
import { ref } from 'vue'

export function useLoading() {
  const loading = ref(false)
  
  const withLoading = async (fn) => {
    loading.value = true
    try {
      return await fn()
    } finally {
      loading.value = false
    }
  }
  
  return { loading, withLoading }
}

// 使用
const { loading, withLoading } = useLoading()
const fetchData = () => withLoading(async () => {
  const data = await api.getData()
  list.value = data
})

:::


4. 知名开源项目的架构参考

4.1 Vue 3 官方仓库

vue/
├── packages/
│   ├── vue/                # 核心包
│   ├── reactivity/         # 响应式系统
│   ├── runtime-core/       # 运行时核心
│   ├── runtime-dom/        # DOM 运行时
│   ├── compiler-sfc/       # 单文件组件编译器
│   └── shared/             # 共享工具
├── scripts/                # 构建脚本
└── tsconfig.json

特点

  • Monorepo 结构,多个包统一管理
  • 按功能拆分 package,职责清晰
  • 共享工具提取到 shared 包

4.2 React 官方仓库

react/
├── packages/
│   ├── react/              # React 核心
│   ├── react-dom/          # DOM 渲染器
│   ├── react-reconciler/   # 协调器
│   ├── scheduler/          # 调度器
│   └── shared/             # 共享代码
├── fixtures/               # 测试用例
└── scripts/

特点

  • 核心与渲染器分离(react vs react-dom
  • reconciler 独立,支持多平台
  • scheduler 单独抽离,可独立使用

4.3 Ant Design Vue

ant-design-vue/
├── components/             # 组件目录
│   ├── button/
│   ├── modal/
│   └── ...
├── docs/                   # 文档
├── site/                   # 官网
├── tests/                  # 测试
└── typings/                # 类型定义

特点

  • 组件与文档分离
  • 每个组件独立目录,包含 demo、test、style
  • 统一的类型定义

4.4 Next.js(全栈框架)

my-nextjs-app/
├── app/                    # App Router(新版)
│   ├── page.js             # 页面
│   ├── layout.js           # 布局
│   ├── loading.js          # 加载状态
│   └── api/                # API 路由
├── components/             # 组件
├── lib/                    # 工具函数
├── public/                 # 静态资源
└── styles/                 # 全局样式

特点

  • 约定式路由,文件即路由
  • 内置 loading、error、layout 等约定文件
  • API 路由与页面共存

5. 架构设计原则与检查清单

5.1 核心原则

原则 说明 实践建议
单一职责 一个模块只做一件事 组件、函数保持简洁
高内聚低耦合 相关代码放在一起,减少依赖 按功能组织目录
可预测性 代码行为符合直觉 命名清晰,结构一致
可测试性 便于编写单元测试 纯函数、依赖注入
可扩展性 新功能容易添加 预留扩展点,避免硬编码

5.2 检查清单

目录结构

  • 是否有清晰的目录划分?
  • 新成员能否快速找到文件位置?
  • 是否避免了过深的嵌套(建议不超过 4 层)?

组件设计

  • 组件是否单一职责?
  • Props 是否清晰、可预测?
  • 是否提取了可复用的逻辑到 hooks

代码组织

  • 是否避免了循环依赖?
  • 工具函数是否纯函数优先?
  • 常量、配置是否集中管理?

团队协作

  • 是否有编码规范文档?
  • 是否有文件命名约定?
  • 代码审查是否关注架构问题?

6. 总结

::: tip 💡 核心思想 好的前端项目架构不是一成不变的,而是随着项目发展不断演进的。关键是建立清晰的组织原则命名约定,让团队成员达成共识。

记住这几点

  1. 先简单后复杂:小项目不要过度设计
  2. 按功能组织:中大型项目推荐 Feature-based
  3. 统一约定:命名、结构、代码风格保持一致
  4. 持续重构:定期审视架构,及时调整

最终目标:让代码像整理好的衣柜一样,想找什么立刻能找到,新成员也能快速上手。 :::


参考资源