前端架构模式

What — 什么是前端架构模式

前端架构模式是组织前端代码结构、模块划分、依赖关系的顶层设计。它决定了代码如何分层、模块如何通信、应用如何拆分与组合。

核心架构模式

模式核心思想适用规模
单体应用一个仓库一个应用小型项目
分层架构按职责分层(视图/逻辑/数据)中型项目
Monorepo多包单仓库,共享工具链中大型
微前端多团队独立开发独立部署大型/超大型
Islands 架构静态 HTML + 交互岛屿内容型网站

Why — 为什么需要前端架构

1. 项目规模增长的必然需求

阶段规模痛点推荐架构
创业期1-3人快速迭代单体 + 简单分层
成长期5-15人代码冲突、构建慢Monorepo + 分层
成熟期15+人团队协作、独立部署微前端

2. 架构选型的关键维度

维度关注点
开发效率启动速度、热更新、构建速度
协作效率代码冲突、发布耦合、团队边界
可维护性模块边界、依赖方向、代码复用
可扩展性新功能接入成本、团队扩展
部署灵活性独立部署、灰度发布、回滚

How — 各架构模式详解

1. 分层架构

分层架构是最基础的前端架构,将代码按职责分为多个层次。

经典三层架构

┌──────────────────────────┐
│     表现层 (Presentation) │  组件、页面、样式
├──────────────────────────┤
│     业务层 (Business)     │  状态管理、业务逻辑、Hooks/Composables
├──────────────────────────┤
│     数据层 (Data)         │  API 调用、数据转换、缓存
└──────────────────────────┘

目录结构

src/
├── pages/              — 页面组件(表现层)
│   ├── Home/
│   ├── User/
│   └── Dashboard/
├── components/         — 通用组件(表现层)
│   ├── Button/
│   ├── Modal/
│   └── Table/
├── composables/        — 业务组合函数(业务层)
│   ├── useAuth.ts
│   ├── useCart.ts
│   └── usePermission.ts
├── stores/             — 状态管理(业务层)
│   ├── userStore.ts
│   └── appStore.ts
├── services/           — API 服务(数据层)
│   ├── userService.ts
│   ├── orderService.ts
│   └── http.ts         — Axios 实例
├── utils/              — 工具函数
├── types/              — TypeScript 类型
└── constants/          — 常量

各层职责与依赖规则

// ===== 数据层:只关心 API 调用和数据转换 =====
// services/http.ts
import axios from 'axios'

const http = axios.create({
  baseURL: '/api',
  timeout: 10000,
})

http.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

export default http

// services/userService.ts
import http from './http'
import type { User, LoginParams } from '@/types'

export const userService = {
  login: (params: LoginParams) => http.post<User>('/auth/login', params),
  getProfile: () => http.get<User>('/user/profile'),
  updateProfile: (data: Partial<User>) => http.put<User>('/user/profile', data),
}
// ===== 业务层:组合数据层,提供业务逻辑 =====
// composables/useAuth.ts
import { ref, computed } from 'vue'
import { userService } from '@/services/userService'

const user = ref<User | null>(null)
const token = ref<string | null>(localStorage.getItem('token'))

export function useAuth() {
  const isLoggedIn = computed(() => !!token.value)

  async function login(params: LoginParams) {
    const { data } = await userService.login(params)
    token.value = data.token
    user.value = data.user
    localStorage.setItem('token', data.token)
  }

  function logout() {
    token.value = null
    user.value = null
    localStorage.removeItem('token')
  }

  return { user, token, isLoggedIn, login, logout }
}
<!-- ===== 表现层:只关心渲染和交互 ===== -->
<template>
  <form @submit.prevent="handleLogin">
    <input v-model="form.username" placeholder="用户名" />
    <input v-model="form.password" type="password" placeholder="密码" />
    <Button type="submit" :loading="loading">登录</Button>
  </form>
</template>

<script setup>
import { useAuth } from '@/composables/useAuth'
import { Button } from '@/components'

const { login } = useAuth()
const loading = ref(false)

const form = reactive({ username: '', password: '' })

async function handleLogin() {
  loading.value = true
  try {
    await login(form)
    router.push('/dashboard')
  } finally {
    loading.value = false
  }
}
</script>

依赖方向规则

表现层 → 业务层 → 数据层
(不能反向依赖)

2. Feature-Sliced Design (FSD)

FSD 是近年流行的前端架构方法论,按业务功能垂直切分,而非按技术层水平切分。

FSD 的层级

src/
├── app/               — 应用初始化(路由、Provider、全局样式)
├── pages/             — 页面路由组件(组合 features)
├── widgets/           — 页面级区块(Header、Sidebar、Feed)
├── features/          — 业务功能(auth、search、cart)
├── entities/          — 业务实体(user、product、order)
└── shared/            — 共享资源(UI 组件、工具函数、API 客户端)

依赖规则(只能向下依赖)

app → pages → widgets → features → entities → shared
// features/auth/index.ts — 功能的公共 API
export { LoginForm } from './ui/LoginForm'
export { useAuth } from './model/useAuth'

// features/auth/model/useAuth.ts
import { userService } from '@/entities/user/api/userService'
import { userStore } from '@/entities/user/model/userStore'

export function useAuth() {
  // 使用 entities 层的 store 和 service
  const { setUser } = userStore()

  async function login(params: LoginParams) {
    const { data } = await userService.login(params)
    setUser(data)
  }

  return { login }
}

// features/auth/ui/LoginForm.vue
// 只导入当前 feature 的公共 API 和 shared 层

FSD vs 传统分层

维度传统分层FSD
切分方式按技术层(components/stores/services)按业务功能(auth/user/product)
修改范围改一个功能需跨多个目录改一个功能只改一个 feature
复用粒度组件级功能级
上手成本中(需理解层级规则)

3. Monorepo 架构

Monorepo 将多个相关包放在同一个仓库中,共享工具链和依赖。

my-monorepo/
├── packages/
│   ├── ui/            — 组件库
│   ├── utils/         — 工具函数库
│   ├── admin-app/     — 后台管理应用
│   └── mobile-app/    — 移动端应用
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
# pnpm-workspace.yaml
packages:
  - 'packages/*'
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "lint": {},
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
// packages/ui/package.json
{
  "name": "@my/ui",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsup src/index.ts --format esm,cjs --dts",
    "dev": "tsup src/index.ts --format esm,cjs --watch"
  },
  "peerDependencies": {
    "react": ">=18"
  }
}
// packages/admin-app/package.json
{
  "name": "@my/admin-app",
  "dependencies": {
    "@my/ui": "workspace:*",
    "@my/utils": "workspace:*"
  }
}

Monorepo 工具对比

维度pnpm workspaceTurborepoNx
定位包管理构建编排全功能平台
缓存本地/远程缓存本地/远程缓存
依赖图手动自动自动
增量构建
学习成本

4. 微前端架构

微前端将大型应用拆分为多个独立的子应用,各子应用可独立开发、测试、部署。

主流方案

方案原理代表
JS 沙箱运行时加载子应用 JS,沙箱隔离qiankun
Web ComponentsShadow DOM 隔离子应用single-spa
Module Federation构建时共享模块Webpack 5 MF
iframe物理隔离iframe

qiankun 实战

// 主应用
import { registerMicroApps, start } from 'qiankun'

registerMicroApps([
  {
    name: 'admin',
    entry: '//localhost:8081',
    container: '#subapp-container',
    activeRule: '/admin',
  },
  {
    name: 'crm',
    entry: '//localhost:8082',
    container: '#subapp-container',
    activeRule: '/crm',
  },
])

start()
// 子应用 main.js
export async function bootstrap() {
  console.log('子应用初始化')
}

export async function mount(props) {
  render(props.container)
}

export async function unmount() {
  // 清理副作用
  app.unmount()
}

function render(container) {
  const app = createApp(App)
  app.mount(container ? container.querySelector('#app') : '#app')
}

Module Federation 实战

// webpack.config.js — 远程应用(提供组件)
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
        './UserCard': './src/components/UserCard',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
}
// webpack.config.js — 消费应用
new ModuleFederationPlugin({
  name: 'hostApp',
  remotes: {
    remoteApp: 'remoteApp@http://localhost:8081/remoteEntry.js',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
})
// 消费远程组件
const RemoteButton = React.lazy(() => import('remoteApp/Button'))

function App() {
  return (
    <Suspense fallback="Loading...">
      <RemoteButton>Remote Button</RemoteButton>
    </Suspense>
  )
}

微前端方案对比

维度qiankunModule Federationsingle-spa
隔离方式JS 沙箱 + CSS 隔离模块共享框架无关路由
技术栈统一推荐可跨框架可跨框架
性能中(运行时加载)好(构建时共享)
入侵性子应用需改造需 Webpack 5子应用需改造
共享依赖手动配置自动手动配置

5. Islands 架构

Islands 架构适用于内容型网站(博客、文档、营销页),大部分页面是静态 HTML,只有交互部分(岛屿)hydrate 为 JS。

┌─────────────────────────────────┐
│  静态 HTML(零 JS)              │
│  ┌──────┐        ┌──────┐      │
│  │ 岛屿1 │        │ 岛屿2 │      │
│  │ 轮播图 │        │ 搜索框 │      │
│  └──────┘        └──────┘      │
│                                 │
│         ┌──────────┐           │
│         │  岛屿3    │           │
│         │ 评论区    │           │
│         └──────────┘           │
└─────────────────────────────────┘

Astro 实战

---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro'
import Carousel from '../components/Carousel'
import SearchBox from '../components/SearchBox'
import Comments from '../components/Comments'
---

<Layout title="Home">
  <!-- 静态内容:零 JS -->
  <h1>Welcome</h1>
  <p>静态内容不需要 JavaScript</p>

  <!-- 交互岛屿:只有这些组件会加载 JS -->
  <Carousel client:visible />
  <SearchBox client:idle />
  <Comments client:visible />
</Layout>
指令含义
client:load页面加载时立即 hydrate
client:idle浏览器空闲时 hydrate
client:visible元素进入视口时 hydrate
client:media匹配媒体查询时 hydrate
client:only跳过 SSR,仅客户端渲染

常见问题与踩坑

问题原因解决方案
分层边界模糊业务逻辑混入组件严格规则:组件只调 composable,不直接调 service
Monorepo 构建慢所有包全量构建Turborepo 缓存 + 增量构建
微前端样式冲突全局 CSS 污染CSS Modules / Shadow DOM / qiankun 的 CSS 沙箱
微前端通信复杂子应用间直接依赖通过主应用的全局状态 / CustomEvent 通信
Islands 交互割裂岛屿间无法共享状态将共享状态提升到 layout 层

最佳实践

  1. 从小开始:不要过度设计,单体 + 分层足够应对 90% 的项目。
  2. 依赖方向单一:上层依赖下层,绝不反向。
  3. Monorepo 用 pnpm:硬链接节省磁盘,workspace 协议确保包版本一致。
  4. 微前端慎用:只有团队规模和独立部署需求真正存在时才用。
  5. 内容站用 Islands:Astro 的 Islands 架构可以让页面 JS 减少 90%。

面试题

1. 分层架构中为什么不能让数据层依赖表现层?

:因为依赖方向必须单向,避免循环依赖和职责混乱。表现层(组件)是消费方,数据层(API 服务)是提供方。如果数据层依赖表现层,意味着 API 调用代码中会导入组件——这在逻辑上不合理(API 不应该关心 UI 怎么展示),在工程上导致循环依赖(组件导入 API,API 导入组件),在复用上受限(数据层无法被其他非 UI 代码复用,如 CLI 工具、定时任务)。


2. Feature-Sliced Design 和传统分层架构的核心区别是什么?

:传统分层按技术层水平切分(所有组件放 components/,所有 API 放 services/),FSD 按业务功能垂直切分(认证功能的所有代码放 features/auth/)。区别:(1) 修改范围——改一个功能,传统分层需改 components/stores/services 三个目录,FSD 只改一个 feature 目录;(2) 删除便利——删一个功能,FSD 直接删文件夹,传统分层要搜索所有目录找出相关文件;(3) 复用粒度——传统分层复用组件级,FSD 复用功能级(整个 feature 可被多个页面导入)。代价是 FSD 学习曲线更陡。


3. Monorepo 和 Multirepo 各自的优缺点是什么?

:Monorepo 优点:(1) 代码共享方便——包之间直接引用,无需发布 npm;(2) 原子提交——一个 PR 可以同时改多个包;(3) 统一工具链——ESLint、TypeScript、CI 配置统一管理。Monorepo 缺点:(1) 构建变慢——需要增量构建和缓存;(2) 权限控制难——所有代码在同一仓库;(3) 仓库体积大。Multirepo 优点:(1) 团队独立——每个仓库独立权限和 CI;(2) 构建快——每个仓库体量小。Multirepo 缺点:(1) 共享困难——需要发布 npm 包;(2) 版本协调——包 A 依赖包 B 的新版本,需等 B 发布;(3) 跨仓库修改——一个功能涉及多个仓库要开多个 PR。


4. qiankun 和 Module Federation 的核心区别是什么?

:qiankun 是运行时方案,Module Federation 是构建时方案。qiankun 在运行时通过 import-html-entry 加载子应用的 HTML/JS/CSS,用 Proxy 沙箱隔离全局变量,适合不同技术栈的子应用。Module Federation 在构建时通过 Webpack 插件将模块暴露为远程入口,消费方构建时就确定了远程依赖,共享 React 等基础库避免重复加载,适合同技术栈的模块共享。qiankun 隔离性好但性能一般,MF 性能好但需要 Webpack 5 且共享依赖版本需兼容。


5. Islands 架构解决了什么问题?与传统 SSR 有什么区别?

:Islands 架构解决了 SSR 应用的 JS 体积问题。传统 SSR 虽然首屏 HTML 快,但 hydration 时需要加载整个页面的 JS,即使大部分内容是纯静态的(标题、段落、图片)。Islands 架构让静态内容保持为纯 HTML(零 JS),只有交互组件(搜索框、轮播图、评论区)hydrate 为 JS。结果是:一个博客页面的 JS 从 200KB 降到 20KB,TTI(Time to Interactive)从 3s 降到 0.5s。代表框架是 Astro。


6. 如何确定一个项目应该用什么架构?

:按团队规模和项目复杂度选择:(1) 1-3 人、1 个产品——单体 + 简单分层,不要过度设计;(2) 5-10 人、1 个产品——Monorepo + FSD 分层,共享组件和工具库;(3) 10-20 人、多个产品——Monorepo + 多应用,共享 UI 库和业务库;(4) 20+ 人、多个团队——微前端,各团队独立开发和部署。关键原则:架构服务于团队协作效率,不是为了技术先进。一个 3 人团队上微前端是过度设计,一个 50 人团队用单体是灾难。


7. Turborepo 的缓存机制是怎么工作的?

:Turborepo 基于输入哈希做缓存:(1) 计算每个 task 的输入哈希(源文件内容 + 依赖的 task 输出 + 环境变量 + 配置文件);(2) 如果本地缓存中有匹配哈希的输出,直接复制缓存结果,跳过执行;(3) 如果配置了远程缓存(Turbo Remote Cache),团队成员的构建结果可以共享——A 构建过 @my/ui,B 拉取缓存后无需重新构建。这使得 CI 中的大部分 task 可以秒级完成。关键:只有输入不变才命中缓存,修改一行代码只会导致依赖链上的 task 重新执行。


8. 微前端中子应用之间如何通信?

:三种通信方式:(1) 主应用中转——子应用通过 props 从主应用获取数据,通过回调函数向主应用发送消息,主应用作为中心枢纽转发;(2) CustomEvent——子应用通过 window.dispatchEvent(new CustomEvent('event-name', { detail })) 发送事件,其他子应用通过 window.addEventListener 监听,完全解耦但无类型安全;(3) 共享状态——主应用创建全局状态(如 Observable / EventEmitter / MicroState),子应用通过 API 读写。最佳实践:简单场景用 props + 回调,复杂场景用共享状态,跨技术栈用 CustomEvent。避免子应用之间直接依赖。


相关链接