引言

随着前端应用的复杂度不断增加,传统的单体前端架构已难以满足大型项目的需求。现代前端架构需要解决代码分割、团队协作、性能优化、可维护性等多方面的挑战。本文将从架构设计的角度,深入探讨如何构建可扩展的大型前端应用。

一、前端架构演进史

1.1 从页面到应用的发展历程

jQuery时代 → AMD/RequireJS → CommonJS/Node.js → ES6 Modules → 现代框架时代

发展阶段

阶段特点代表技术局限性
静态页面HTML + CSS + 少量JSjQuery、Prototype代码复用差
模块化初期RequireJS、SeaJSAMD、CMD加载复杂
组件化React、Vue诞生Virtual DOM状态管理混乱
工程化Webpack、RollupES6 Modules构建复杂度高
现代化Next.js、Nuxt.jsSSR、SSR Streaming学习曲线陡峭

1.2 当前架构挑战

大型前端应用面临的核心问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 问题1: 代码组织混乱
// src/
//   components/     // 1000+ 组件
//   pages/         // 200+ 页面
//   utils/         // 工具函数散落各处
//   api/           // API调用不规范

// 问题2: 状态管理复杂
// - 全局状态与局部状态边界模糊
// - 状态流转难以追踪
// - 多个状态管理方案混用

// 问题3: 性能瓶颈
// - 首屏加载时间长
// - 路由切换卡顿
// - 内存泄漏风险

// 问题4: 团队协作困难
// - 代码冲突频繁
// - 发布周期长
// - 技术栈不统一

二、架构设计原则

2.1 SOLID原则在前端的应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// S - 单一职责原则
// ❌ 违反单一职责
class UserComponent {
  render() { }
  fetchUser() { }
  validateInput() { }
  formatData() { }
  logAnalytics() { }
}

// ✅ 遵循单一职责
class UserComponent {
  constructor(
    private renderer: UserRenderer,
    private userService: UserService,
    private validator: InputValidator
  ) { }

  render() { this.renderer.render() }
  fetchUser() { this.userService.fetch() }
}

// O - 开闭原则
// 使用插件系统扩展功能
interface ComponentPlugin {
  install(component: any): void
  uninstall(component: any): void
}

class Component {
  private plugins: ComponentPlugin[] = []

  use(plugin: ComponentPlugin) {
    this.plugins.push(plugin)
    plugin.install(this)
    return this
  }
}

// L - 里氏替换原则
// 基类可被子类无缝替换
abstract class BaseStore<T> {
  abstract get(id: string): Promise<T>
  abstract set(id: string, data: T): Promise<void>
}

class UserStore extends BaseStore<User> {
  async get(id: string): Promise<User> { }
  async set(id: string, data: User): Promise<void> { }
}

// I - 接口隔离原则
// 拆分大接口为小接口
interface ReadOnlyRepository<T> {
  findById(id: string): Promise<T>
  findAll(): Promise<T[]>
}

interface WriteOnlyRepository<T> {
  create(data: T): Promise<T>
  update(id: string, data: Partial<T>): Promise<T>
  delete(id: string): Promise<void>
}

// D - 依赖倒置原则
// 依赖抽象而非具体实现
interface CacheService {
  get(key: string): Promise<any>
  set(key: string, value: any): Promise<void>
}

class UserService {
  constructor(private cache: CacheService) { }
}

2.2 分层架构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 标准前端分层架构
┌─────────────────────────────────────────────┐
│          Presentation Layer                 │
│  (Components, Pages, UI Logic)              │
├─────────────────────────────────────────────┤
│          Business Logic Layer               │
│  (Hooks, Composables, Use Cases)            │
├─────────────────────────────────────────────┤
│          Data Access Layer                  │
│  (API Client, Cache, State Management)      │
├─────────────────────────────────────────────┤
│          Infrastructure Layer               │
│  (HTTP, WebSocket, LocalStorage)            │
└─────────────────────────────────────────────┘

// 实现示例
// ========== Presentation Layer ==========
// views/UserList.vue
<template>
  <UserDataTable
    :users="users"
    :loading="loading"
    @refresh="handleRefresh"
  />
</template>

// ========== Business Logic Layer ==========
// composables/useUserList.ts
export function useUserList() {
  const { users, loading, error, fetch } = userRepository()

  const refresh = async () => {
    await fetch()
    analytics.track('user_list_refreshed')
  }

  const filteredUsers = computed(() => {
    return users.value.filter(user => user.active)
  })

  return {
    users: filteredUsers,
    loading,
    error,
    refresh
  }
}

// ========== Data Access Layer ==========
// repositories/userRepository.ts
export function userRepository() {
  const api = useApiClient()
  const cache = useCache()

  const fetch = async () => {
    const cached = await cache.get('users')
    if (cached) return cached

    const data = await api.get('/users')
    await cache.set('users', data, 300) // 5分钟
    return data
  }

  return {
    users: ref([]),
    loading: ref(false),
    error: ref(null),
    fetch
  }
}

// ========== Infrastructure Layer ===========
// utils/apiClient.ts
class ApiClient {
  private baseURL: string
  private interceptors: RequestInterceptor[]

  async get<T>(url: string): Promise<T> {
    const request = new Request(this.baseURL + url)
    return this.execute<T>(request)
  }
}

2.3 领域驱动设计(DDD)在前端

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 1. 领域模型定义
// domains/user/User.ts
export class User {
  constructor(
    private readonly id: UserId,
    private readonly email: Email,
    private name: UserName,
    private status: UserStatus
  ) {}

  // 领域逻辑
  activate(): void {
    if (this.status === UserStatus.ACTIVE) {
      throw new Error('User already active')
    }
    this.status = UserStatus.ACTIVE
  }

  deactivate(): void {
    this.status = UserStatus.INACTIVE
  }

  isActive(): boolean {
    return this.status === UserStatus.ACTIVE
  }
}

// 值对象
export class Email {
  constructor(private readonly value: string) {
    if (!this.validate(value)) {
      throw new Error('Invalid email')
    }
  }

  private validate(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
  }

  getValue(): string {
    return this.value
  }
}

// 2. 领域服务
// domains/user/UserService.ts
export class UserService {
  constructor(
    private userRepo: UserRepository,
    private emailService: EmailService
  ) {}

  async registerUser(data: RegisterUserData): Promise<User> {
    // 业务规则验证
    if (await this.userRepo.existsByEmail(data.email)) {
      throw new Error('Email already registered')
    }

    // 创建领域对象
    const user = new User(
      UserId.generate(),
      new Email(data.email),
      new UserName(data.name),
      UserStatus.PENDING
    )

    // 持久化
    await this.userRepo.save(user)

    // 发送欢迎邮件
    await this.emailService.sendWelcome(user.email.getValue())

    return user
  }
}

// 3. 应用服务(用例)
// application/useCases/RegisterUserUseCase.ts
export class RegisterUserUseCase {
  constructor(private userService: UserService) { }

  async execute(request: RegisterUserRequest): Promise<RegisterUserResponse> {
    try {
      const user = await this.userService.registerUser(request.data)

      return {
        success: true,
        userId: user.id.getValue(),
        message: 'Registration successful'
      }
    } catch (error) {
      return {
        success: false,
        error: error.message
      }
    }
  }
}

// 4. 在组件中使用
// components/RegisterForm.vue
export default {
  setup() {
    const registerUser = useRegisterUserUseCase()

    const handleSubmit = async (formData) => {
      const result = await registerUser.execute({
        data: {
          email: formData.email,
          name: formData.name,
          password: formData.password
        }
      })

      if (result.success) {
        router.push(`/welcome/${result.userId}`)
      } else {
        showError(result.error)
      }
    }

    return { handleSubmit }
  }
}

三、微前端架构

3.1 微前端架构模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 模式1: 路由分发
// 主应用根据路由加载不同子应用
const microApps = [
  {
    name: 'user-center',
    entry: '//localhost:3001',
    container: '#subapp',
    activeRule: '/users'
  },
  {
    name: 'order-system',
    entry: '//localhost:3002',
    container: '#subapp',
    activeRule: '/orders'
  }
]

// 模式2: 组合式集成
// 主应用提供容器,子应用作为组件渲染
<template>
  <div>
    <AppHeader />
    <MicroApp name="user-center" :props="userProps" />
    <AppFooter />
  </div>
</template>

// 模式3: EMP (EMP Module Federation)
// 去中心化的模块共享
// 子应用A
new ModuleFederationPlugin({
  name: 'appA',
  exposes: {
    './UserList': './src/components/UserList'
  },
  shared: ['vue', 'vue-router']
})

// 子应用B使用A的组件
import UserList from 'appA/UserList'

3.2 模块联邦实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// webpack.config.js - 主应用
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host_app',
      remotes: {
        userApp: 'user_app@http://localhost:3001/remoteEntry.js',
        orderApp: 'order_app@http://localhost:3002/remoteEntry.js'
      },
      shared: {
        vue: {
          singleton: true,
          requiredVersion: '^3.0.0'
        },
        'vue-router': {
          singleton: true
        }
      }
    })
  ]
}

// 在主应用中使用远程模块
// src/App.vue
<script>
import { defineAsyncComponent } from 'vue'

// 动态加载远程组件
const UserDashboard = defineAsyncComponent(() =>
  import('userApp/Dashboard')
)

const OrderList = defineAsyncComponent(() =>
  import('orderApp/OrderList')
)

export default {
  components: {
    UserDashboard,
    OrderList
  }
}
</script>

// 子应用配置 - userApp
// webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'user_app',
      filename: 'remoteEntry.js',
      exposes: {
        './Dashboard': './src/components/Dashboard.vue',
        './UserProfile': './src/components/UserProfile.vue',
        './UserStore': './src/stores/user'
      },
      shared: {
        vue: {
          singleton: true,
          requiredVersion: '^3.0.0'
        }
      }
    })
  ]
}

3.3 微前端通信方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// ========== 方案1: 自定义事件总线 ==========
// utils/eventBus.ts
class MicroEventBus {
  private events: Map<string, Set<Function>>

  constructor() {
    this.events = new Map()
  }

  on(event: string, callback: Function) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set())
    }
    this.events.get(event)!.add(callback)
  }

  off(event: string, callback: Function) {
    this.events.get(event)?.delete(callback)
  }

  emit(event: string, data?: any) {
    this.events.get(event)?.forEach(callback => callback(data))
  }
}

// 全局单例
export const microEventBus = new MicroEventBus()

// 子应用A - 发送事件
import { microEventBus } from './eventBus'

microEventBus.emit('user:login', {
  userId: '123',
  userName: 'Alice'
})

// 子应用B - 监听事件
microEventBus.on('user:login', (user) => {
  console.log('User logged in:', user)
  updateUI(user)
})

// ========== 方案2: 状态共享 ==========
// stores/sharedStore.ts
import { reactive, readonly } from 'vue'

const state = reactive({
  user: null,
  theme: 'light',
  locale: 'zh-CN'
})

export const sharedStore = {
  state: readonly(state),

  setUser(user: any) {
    state.user = user
  },

  setTheme(theme: string) {
    state.theme = theme
  },

  setLocale(locale: string) {
    state.locale = locale
  }
}

// ========== 方案3: 全局状态管理 ==========
// 使用Pinia创建跨应用store
// stores/global.ts
import { defineStore } from 'pinia'

export const useGlobalStore = defineStore('global', {
  state: () => ({
    currentUser: null,
    notifications: []
  }),

  actions: {
    async login(credentials) {
      const user = await api.login(credentials)
      this.currentUser = user
    },

    logout() {
      this.currentUser = null
    },

    addNotification(notification) {
      this.notifications.push(notification)
    }
  }
})

3.4 样式隔离

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// ========== 方案1: CSS Modules ==========
// components/UserCard.module.css
.card {
  padding: 20px;
  border-radius: 8px;
  background: white;
}

.title {
  font-size: 18px;
  color: #333;
}

// components/UserCard.vue
<template>
  <div :class="$style.card">
    <h3 :class="$style.title">{{ title }}</h3>
  </div>
</template>

<script>
import styles from './UserCard.module.css'
export default {
  $style: styles
}
</script>

// ========== 方案2: Shadow DOM ==========
// components/ShadowWidget.ts
class ShadowWidget extends HTMLElement {
  constructor() {
    super()

    // 创建Shadow DOM
    const shadow = this.attachShadow({ mode: 'open' })

    // 隔离的样式
    const style = document.createElement('style')
    style.textContent = `
      .widget {
        padding: 20px;
        background: #f5f5f5;
      }
      .widget h3 {
        color: #333;
        margin: 0 0 10px 0;
      }
    `

    // 隔离的内容
    const wrapper = document.createElement('div')
    wrapper.className = 'widget'
    wrapper.innerHTML = `
      <h3>Shadow DOM Widget</h3>
      <p>This is isolated from host page styles</p>
    `

    shadow.appendChild(style)
    shadow.appendChild(wrapper)
  }
}

customElements.define('shadow-widget', ShadowWidget)

// ========== 方案3: CSS-in-JS ==========
// 使用styled-components或emotion
import styled from 'styled-components'

const Card = styled.div`
  padding: 20px;
  border-radius: 8px;
  background: white;

  h3 {
    font-size: 18px;
    color: #333;
  }
`

// 组件自动生成唯一类名
<Card>
  <h3>Title</h3>
</Card>

// ========== 方案4: 命名空间 ==========
// 推荐的BEM + 命名空间组合
// [子应用名]-[块]__[元素]--[修饰器]
.user-center-card__header--active {
  background: #007bff;
}

.order-system-list__item--highlighted {
  background: #ffc107;
}

四、状态管理架构

4.1 状态管理选型

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
// ========== 方案对比 ==========
// 1. Redux Toolkit - 复杂应用
import { createSlice, configureStore } from '@reduxjs/toolkit'

const userSlice = createSlice({
  name: 'user',
  initialState: {
    user: null,
    status: 'idle'
  },
  reducers: {
    setUser(state, action) {
      state.user = action.payload
    },
    clearUser(state) {
      state.user = null
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.status = 'loading'
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded'
        state.user = action.payload
      })
  }
})

export const { setUser, clearUser } = userSlice.actions
export default configureStore({
  reducer: {
    user: userSlice.reducer
  }
})

// 2. Zustand - 轻量级
import create from 'zustand'

const useUserStore = create((set, get) => ({
  user: null,
  status: 'idle',

  setUser: (user) => set({ user }),

  fetchUser: async (id) => {
    set({ status: 'loading' })
    const user = await api.getUser(id)
    set({ user, status: 'succeeded' })
  },

  getUserAge: () => {
    const { user } = get()
    return user ? calculateAge(user.birthday) : 0
  }
}))

// 3. Pinia - Vue推荐
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    status: 'idle'
  }),

  getters: {
    isLoggedIn: (state) => !!state.user,
    userName: (state) => state.user?.name || 'Guest'
  },

  actions: {
    async fetchUser(id) {
      this.status = 'loading'
      this.user = await api.getUser(id)
      this.status = 'succeeded'
    }
  }
})

// 4. Jotai - 原子化状态
import { atom, useAtom } from 'jotai'

// 原子状态
const userAtom = atom(null)
const statusAtom = atom('idle')

// 派生原子
const isLoggedInAtom = atom(
  (get) => !!get(userAtom)
)

// 组件中使用
function UserProfile() {
  const [user, setUser] = useAtom(userAtom)
  const [status] = useAtom(statusAtom)
  const [isLoggedIn] = useAtom(isLoggedInAtom)

  // ...
}

4.2 分层状态架构

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// ========== 状态分层设计 ==========
// 1. 服务端状态 - React Query / SWR
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

// 获取数据
function useUser(id: string) {
  return useQuery({
    queryKey: ['user', id],
    queryFn: () => api.getUser(id),
    staleTime: 5 * 60 * 1000, // 5分钟
    cacheTime: 10 * 60 * 1000 // 10分钟
  })
}

// 修改数据
function useUpdateUser() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ id, data }) => api.updateUser(id, data),
    onSuccess: (data, variables) => {
      // 自动更新缓存
      queryClient.setQueryData(['user', variables.id], data)
      // 或使缓存失效
      queryClient.invalidateQueries(['user', variables.id])
    }
  })
}

// 2. 全局UI状态 - Zustand
import create from 'zustand/vanilla'

const uiStore = create((set) => ({
  theme: 'light',
  sidebarOpen: true,
  modalOpen: false,

  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  })),

  toggleSidebar: () => set((state) => ({
    sidebarOpen: !state.sidebarOpen
  })),

  openModal: () => set({ modalOpen: true }),
  closeModal: () => set({ modalOpen: false })
}))

// 3. 表单状态 - 原子状态
import { atom, useAtom } from 'jotai'

const formDataAtom = atom({
  username: '',
  email: '',
  password: ''
})

const formErrorsAtom = atom({})

const useForm = () => {
  const [data, setData] = useAtom(formDataAtom)
  const [errors, setErrors] = useAtom(formErrorsAtom)

  const updateField = (field, value) => {
    setData((prev) => ({
      ...prev,
      [field]: value
    }))
  }

  const validate = () => {
    const newErrors = {}
    if (!data.username) newErrors.username = 'Required'
    if (!data.email) newErrors.email = 'Required'
    setErrors(newErrors)
    return Object.keys(newErrors).length === 0
  }

  return {
    data,
    errors,
    updateField,
    validate
  }
}

// ========== 状态组合使用 ==========
function UserProfile({ userId }) {
  // 服务端状态
  const { data: user, isLoading } = useUser(userId)
  const updateUser = useUpdateUser()

  // UI状态
  const { theme, toggleTheme } = useUiStore()

  // 本地状态
  const [isEditing, setIsEditing] = useState(false)

  if (isLoading) return <Spinner />

  return (
    <div className={theme}>
      {isEditing ? (
        <EditForm
          user={user}
          onSave={(data) => updateUser.mutate({ id: userId, data })}
          onCancel={() => setIsEditing(false)}
        />
      ) : (
        <UserDetail user={user} onEdit={() => setIsEditing(true)} />
      )}
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  )
}

4.3 状态持久化

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
// ========== 本地持久化方案 ==========
// 1. Zustand + localStorage
import { persist } from 'zustand/middleware'

export const useUserStore = create(
  persist(
    (set) => ({
      user: null,
      token: null,
      setUser: (user) => set({ user }),
      setToken: (token) => set({ token }),
      logout: () => set({ user: null, token: null })
    }),
    {
      name: 'user-storage',
      // 部分持久化
      partialize: (state) => ({
        user: state.user,
        token: state.token
      })
    }
  )
)

// 2. Pinia + localStorage
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export const useAuthStore = defineStore('auth', {
  state: () => ({
    token: null,
    refreshToken: null
  }),

  actions: {
    setTokens(token, refreshToken) {
      this.token = token
      this.refreshToken = refreshToken
    }
  },

  persist: {
    key: 'auth',
    storage: localStorage,
    pick: ['token', 'refreshToken']
  }
})

// 3. IndexedDB - 大量数据
import { openDB } from 'idb'

const dbPromise = openDB('app-db', 1, {
  upgrade(db) {
    db.createObjectStore('cache')
  }
})

export const idbStorage = {
  async getItem(key) {
    const db = await dbPromise
    return await db.get('cache', key)
  },

  async setItem(key, value) {
    const db = await dbPromise
    return await db.put('cache', value, key)
  },

  async removeItem(key) {
    const db = await dbPromise
    return await db.delete('cache', key)
  }
}

// 4. 服务端状态同步
import { useQuery, useMutation } from '@tanstack/react-query'

// 自动同步到服务端
function useSyncedState(key, initialValue) {
  const queryClient = useQueryClient()

  const { data } = useQuery({
    queryKey: ['state', key],
    queryFn: () => api.getState(key),
    initialData: initialValue
  })

  const mutation = useMutation({
    mutationFn: (value) => api.setState(key, value),
    onSuccess: () => {
      queryClient.invalidateQueries(['state', key])
    }
  })

  const setState = (value) => {
    mutation.mutate(value)
  }

  return [data, setState]
}

五、性能优化架构

5.1 代码分割策略

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
// ========== 路由级别分割 ==========
// Vue Router
const routes = [
  {
    path: '/dashboard',
    component: () => import(
      /* webpackChunkName: "dashboard" */
      './views/Dashboard.vue'
    )
  },
  {
    path: '/users',
    component: () => import(
      /* webpackChunkName: "users" */
      './views/Users.vue'
    )
  }
]

// React Router
const Dashboard = lazy(() => import('./views/Dashboard'))
const Users = lazy(() => import('./views/Users'))

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/users" element={<Users />} />
      </Routes>
    </Suspense>
  )
}

// ========== 组件级别分割 ==========
// Vue 3 - defineAsyncComponent
const HeavyComponent = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,
  timeout: 5000
})

// React - lazy + Suspense
const HeavyChart = lazy(() => import('./HeavyChart'))

function Dashboard() {
  const [showChart, setShowChart] = useState(false)

  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        Show Chart
      </button>
      {showChart && (
        <Suspense fallback={<Spinner />}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  )
}

// ========== 条件加载 ==========
// 只在需要时加载库
const loadMonaco = async () => {
  const monaco = await import('monaco-editor')
  return monaco
}

const loadChartLibrary = async () => {
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    return null // 用户不喜欢动画
  }

  const chart = await import('echarts')
  return chart
}

// ========== 预加载策略 ==========
// 预加载下一个可能访问的页面
function ProductList({ products }) {
  const prefetch = usePrefetch()

  const handleMouseEnter = (productId) => {
    // 鼠标悬停时预加载
    prefetch(`/products/${productId}`)
  }

  return (
    <ul>
      {products.map(product => (
        <li
          key={product.id}
          onMouseEnter={() => handleMouseEnter(product.id)}
        >
          <Link to={`/products/${product.id}`}>
            {product.name}
          </Link>
        </li>
      ))}
    </ul>
  )
}

// Webpack魔法注释
const ProductDetail = lazy(() => import(
  /* webpackPrefetch: true */
  /* webpackChunkName: "product-detail" */
  './ProductDetail'
))

5.2 渲染优化

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// ========== 虚拟列表 ==========
import { useVirtualizer } from '@tanstack/react-virtual'

function VirtualList({ items }) {
  const parentRef = useRef()

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50, // 每项高度
    overscan: 5 // 额外渲染项数
  })

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: 'relative'
        }}
      >
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`
            }}
          >
            {items[virtualItem.index].content}
          </div>
        ))}
      </div>
    </div>
  )
}

// ========== 防抖/节流 ==========
import { useDebouncedCallback, useThrottledCallback } from '@hooks'

function SearchInput() {
  // 防抖搜索
  const debouncedSearch = useDebouncedCallback(
    (query) => {
      performSearch(query)
    },
    500 // 延迟
  )

  return (
    <input
      type="text"
      onChange={(e) => debouncedSearch(e.target.value)}
    />
  )
}

function ScrollHandler() {
  // 节流滚动事件
  const throttledScroll = useThrottledCallback(
    () => {
      updatePosition()
    },
    100 // 间隔
  )

  useEffect(() => {
    window.addEventListener('scroll', throttledScroll)
    return () => window.removeEventListener('scroll', throttledScroll)
  }, [throttledScroll])
}

// ========== React优化 ==========
import { memo, useMemo, useCallback, useRef } from 'react'

// 1. 组件记忆化
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  return <div>{/* 复杂渲染 */}</div>
})

// 2. 计算缓存
function DataProcessor({ items }) {
  const sorted = useMemo(() => {
    return items.sort((a, b) => a.value - b.value)
  }, [items]) // 只在items变化时重新计算

  const filtered = useMemo(() => {
    return sorted.filter(item => item.active)
  }, [sorted])

  return <List items={filtered} />
}

// 3. 函数稳定化
function ParentComponent() {
  const [count, setCount] = useState(0)

  // 不稳定的函数引用
  const handleClickBad = () => {
    setCount(count + 1)
  }

  // 稳定的函数引用
  const handleClickGood = useCallback(() => {
    setCount(c => c + 1)
  }, []) // 依赖为空,函数引用不变

  return <ChildComponent onClick={handleClickGood} />
}

// 4. 列表渲染优化
function TodoList({ todos, onToggle }) {
  const todoRefs = useRef(new Map())

  const handleToggle = useCallback((id) => {
    // 批量更新
    startTransition(() => {
      onToggle(id)
    })
  }, [onToggle])

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          ref={(node) => {
            if (node) {
              todoRefs.current.set(todo.id, node)
            } else {
              todoRefs.current.delete(todo.id)
            }
          }}
          todo={todo}
          onToggle={handleToggle}
        />
      ))}
    </ul>
  )
}

const TodoItem = memo(function TodoItem({ todo, onToggle }) {
  return (
    <li onClick={() => onToggle(todo.id)}>
      {todo.text}
    </li>
  )
})

// ========== Vue优化 ==========
// 1. computed缓存
export default {
  setup() {
    const items = ref([])

    // 计算属性自动缓存
    const filtered = computed(() => {
      return items.value.filter(item => item.active)
    })

    const sorted = computed(() => {
      return filtered.value.sort((a, b) => a.value - b.value)
    })

    return { sorted }
  }
}

// 2. v-once/v-memo
<template>
  <!-- 只渲染一次 -->
  <Logo v-once />

  <!-- 按条件记忆 -->
  <div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
    {{ item.text }}
  </div>
</template>

// 3. watchEffect优化
watchEffect((onCleanup) => {
  const timer = setInterval(() => {
    updateData()
  }, 1000)

  onCleanup(() => {
    clearInterval(timer) // 清理副作用
  })
})

// 4. shallowRef/shallowReactive
// 大对象优化
const state = shallowReactive({
  // 只顶层是响应式的
  items: largeItemsArray
})

// 只需要重新赋值时使用
const data = shallowRef(null)

5.3 资源优化

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// ========== 图片优化 ==========
// 1. 响应式图片
<picture>
  <source
    media="(min-width: 1024px)"
    srcSet="image-large.webp"
    type="image/webp"
  />
  <source
    media="(min-width: 768px)"
    srcSet="image-medium.webp"
    type="image/webp"
  />
  <source
    srcSet="image-small.webp"
    type="image/webp"
  />
  <img
    src="image-fallback.jpg"
    alt="Description"
    loading="lazy"
  />
</picture>

// 2. 懒加载图片
import { lazyload } from '@utils/lazyload'

function ImageWithLazy({ src, alt }) {
  const imgRef = useRef()
  const [isLoaded, setIsLoaded] = useState(false)

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const img = entry.target
            img.src = img.dataset.src
            observer.unobserve(img)
          }
        })
      },
      { rootMargin: '50px' }
    )

    if (imgRef.current) {
      observer.observe(imgRef.current)
    }

    return () => observer.disconnect()
  }, [])

  return (
    <img
      ref={imgRef}
      data-src={src}
      alt={alt}
      onLoad={() => setIsLoaded(true)}
      style={{ opacity: isLoaded ? 1 : 0 }}
    />
  )
}

// 3. 图片组件封装
function OptimizedImage({
  src,
  alt,
  width,
  height,
  priority = false
}) {
  const [imageSrc, setImageSrc] = useState(null)

  useEffect(() => {
    const load = async () => {
      // 使用WebP转换服务
      const webpSrc = await convertToWebP(src, width)
      setImageSrc(webpSrc)
    }

    if (priority) {
      load()
    } else {
      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting) {
          load()
          observer.disconnect()
        }
      })
      // ... observe logic
    }
  }, [src, width, priority])

  return <img src={imageSrc} alt={alt} loading={!priority ? 'lazy' : 'eager'} />
}

// ========== 字体优化 ==========
// 1. 字体预加载
<link
  rel="preload"
  href="/fonts/main.woff2"
  as="font"
  type="font/woff2"
  crossOrigin="anonymous"
/>

// 2. 字体显示策略
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap; /* 立即显示后备字体 */
  /* font-display: optional; 短暂等待后使用后备字体 */
  /* font-display: fallback; 短暂等待,隐藏短时间 */
}

// 3. 字体子集化
// 只包含使用的字符
// 使用fonttools或在线工具生成subset

// ========== 第三方资源优化 ==========
// 动态加载重型库
const loadGoogleMaps = () => {
  return new Promise((resolve) => {
    if (window.google) {
      resolve(window.google)
      return
    }

    const script = document.createElement('script')
    script.src = `https://maps.googleapis.com/maps/api/js?key=${API_KEY}`
    script.async = true
    script.onload = () => resolve(window.google)
    document.head.appendChild(script)
  })
}

// 使用场景
function MapComponent() {
  const [map, setMap] = useState(null)

  useEffect(() => {
    let mounted = true

    loadGoogleMaps().then(google => {
      if (mounted) {
        setMap(new google.maps.Map(/* ... */))
      }
    })

    return () => { mounted = false }
  }, [])

  return <div ref={mapRef} />
}

// ========== 缓存策略 ==========
// Service Worker缓存
// sw.js
const CACHE_NAME = 'app-v1'
const STATIC_CACHE = [
  '/',
  '/styles/main.css',
  '/scripts/main.js'
]

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(STATIC_CACHE)
    })
  )
})

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request)
    })
  )
})

// 网络优先策略
async function networkFirst(request) {
  try {
    const response = await fetch(request)
    const cache = await caches.open(CACHE_NAME)
    cache.put(request, response.clone())
    return response
  } catch {
    return caches.match(request)
  }
}

// 缓存优先策略
async function cacheFirst(request) {
  const cached = await caches.match(request)
  if (cached) return cached

  const response = await fetch(request)
  const cache = await caches.open(CACHE_NAME)
  cache.put(request, response.clone())
  return response
}

// 过期策略
async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME)
  const cached = await cache.match(request)

  const fetchPromise = fetch(request).then((response) => {
    cache.put(request, response.clone())
    return response
  })

  return cached || fetchPromise
}

六、构建优化

6.1 Webpack优化配置

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
// webpack.config.js
const path = require('path')
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
  // ========== 1. 构建模式 ==========
  mode: 'production',

  // ========== 2. 代码分割 ==========
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 框架代码单独打包
        framework: {
          test: /[\\/]node_modules[\\/](react|react-dom|vue)[\\/]/,
          name: 'framework',
          priority: 40
        },
        // UI库单独打包
        ui: {
          test: /[\\/]node_modules[\\/](@mui|@ant-design|element-plus)[\\/]/,
          name: 'ui',
          priority: 30
        },
        // 工具库
        lib: {
          test: /[\\/]node_modules[\\/](lodash|axios|dayjs)[\\/]/,
          name: 'lib',
          priority: 20
        },
        // 公共代码
        commons: {
          minChunks: 2,
          priority: 10,
          reuseExistingChunk: true
        }
      }
    },

    // 运行时代码单独提取
    runtimeChunk: 'single',

    // 压缩配置
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          parse: { ecma: 8 },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
            drop_console: true
          },
          mangle: { safari10: true },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true
          }
        }
      }),
      new CssMinimizerPlugin()
    ]
  },

  // ========== 3. 模块解析 ==========
  resolve: {
    // 路径别名
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils')
    },

    // 扩展名
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.json'],

    // 模块查找路径
    modules: ['node_modules', path.resolve(__dirname, 'src')]
  },

  // ========== 4. 缓存配置 ==========
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },

  // ========== 5. 输出配置 ==========
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',

    // 资源文件名
    assetModuleFilename: 'assets/[name].[hash:8][ext]'
  },

  // ========== 6. 性能优化 ==========
  performance: {
    hints: 'warning',
    maxAssetSize: 244 * 1024, // 244KB
    maxEntrypointSize: 244 * 1024
  }
}

6.2 Vite优化配置

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    vue(),

    // 构建分析
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true
    }),

    // Gzip压缩
    viteCompression({
      algorithm: 'gzip',
      ext: '.gz'
    }),

    // Brotli压缩
    viteCompression({
      algorithm: 'brotliCompress',
      ext: '.br'
    })
  ],

  build: {
    // 产物目录
    outDir: 'dist',

    // 静态资源目录
    assetsDir: 'assets',

    // 生成source map
    sourcemap: false,

    // 构建后是否生成 manifest.json
    manifest: true,

    // chunk大小警告阈值
    chunkSizeWarningLimit: 1000,

    // 代码分割
    rollupOptions: {
      output: {
        // 手动分包
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'ui-lib': ['element-plus'],
          'utils': ['axios', 'lodash-es', 'dayjs']
        },

        // 文件命名
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]'
      }
    },

    // 压缩
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  },

  // 依赖优化
  optimizeDeps: {
    include: ['vue', 'vue-router', 'pinia'],
    exclude: ['some-large-dep']
  },

  // CSS配置
  css: {
    modules: {
      localsConvention: 'camelCase'
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  },

  // 开发服务器
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

七、监控与调试

7.1 性能监控

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// ========== Core Web Vitals监控 ==========
// utils/performanceMonitor.ts
interface PerformanceMetrics {
  FCP: number // First Contentful Paint
  LCP: number // Largest Contentful Paint
  FID: number // First Input Delay
  CLS: number // Cumulative Layout Shift
  TTFB: number // Time to First Byte
}

export class PerformanceMonitor {
  private metrics: Partial<PerformanceMetrics> = {}

  async collectMetrics(): Promise<PerformanceMetrics> {
    // 等待页面加载完成
    await new Promise(resolve => window.addEventListener('load', resolve))

    // FCP
    const fcpEntry = performance.getEntriesByName('first-contentful-paint')[0] as any
    this.metrics.FCP = fcpEntry?.startTime || 0

    // LCP
    this.metrics.LCP = await this.getLCP()

    // FID
    this.metrics.FID = await this.getFID()

    // CLS
    this.metrics.CLS = await this.getCLS()

    // TTFB
    const navigation = performance.getEntriesByType('navigation')[0] as any
    this.metrics.TTFB = navigation?.responseStart || 0

    return this.metrics as PerformanceMetrics
  }

  private getLCP(): Promise<number> {
    return new Promise(resolve => {
      new PerformanceObserver((list) => {
        const entries = list.getEntries()
        const lastEntry = entries[entries.length - 1] as any
        resolve(lastEntry?.startTime || 0)
      }).observe({ entryTypes: ['largest-contentful-paint'] })
    })
  }

  private getFID(): Promise<number> {
    return new Promise(resolve => {
      new PerformanceObserver((list) => {
        const firstInput = list.getEntries()[0] as any
        resolve(firstInput?.processingStart - firstInput?.startTime || 0)
      }).observe({ entryTypes: ['first-input'] })
    })
  }

  private getCLS(): Promise<number> {
    return new Promise(resolve => {
      let clsValue = 0
      new PerformanceObserver((list) => {
        for (const entry of list.getEntries() as any[]) {
          if (!entry.hadRecentInput) {
            clsValue += entry.value
          }
        }
        resolve(clsValue)
      }).observe({ entryTypes: ['layout-shift'] })
    })
  }

  reportToAnalytics(metrics: PerformanceMetrics) {
    // 发送到分析平台
    analytics.track('page_performance', {
      fcp: metrics.FCP,
      lcp: metrics.LCP,
      fid: metrics.FID,
      cls: metrics.CLS,
      ttfb: metrics.TTFB,
      url: window.location.href,
      userAgent: navigator.userAgent
    })
  }
}

// ========== 错误监控 ==========
// utils/errorTracker.ts
export class ErrorTracker {
  private errors: Error[] = []

  init() {
    // 全局错误捕获
    window.addEventListener('error', (event) => {
      this.captureError(event.error || new Error(event.message))
    })

    // Promise拒绝捕获
    window.addEventListener('unhandledrejection', (event) => {
      this.captureError(event.reason)
    })

    // Vue错误处理
    if (typeof app !== 'undefined' && app.config) {
      app.config.errorHandler = (err, instance, info) => {
        this.captureError(err, {
          componentName: instance?.$options?.name,
          lifecycle: info
        })
      }
    }
  }

  captureError(error: Error, context?: any) {
    const errorInfo = {
      message: error.message,
      stack: error.stack,
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent,
      context
    }

    this.errors.push(error as any)

    // 发送到错误追踪服务
    this.sendToService(errorInfo)
  }

  private sendToService(errorInfo: any) {
    // 发送到Sentry、自建服务等
    fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorInfo)
    }).catch(console.error)
  }
}

// ========== 用户行为追踪 ==========
// utils/analytics.ts
export class UserBehaviorTracker {
  private events: any[] = []

  trackPageView() {
    this.trackEvent('page_view', {
      url: window.location.href,
      referrer: document.referrer,
      title: document.title
    })
  }

  trackClick(element: HTMLElement) {
    this.trackEvent('click', {
      tagName: element.tagName,
      id: element.id,
      className: element.className,
      text: element.textContent?.substring(0, 50)
    })
  }

  trackScroll(depth: number) {
    this.trackEvent('scroll', {
      scrollDepth: depth,
      pageHeight: document.body.scrollHeight
    })
  }

  trackEngagement(duration: number) {
    this.trackEvent('engagement', {
      duration,
      interactions: this.events.length
    })
  }

  private trackEvent(type: string, data: any) {
    const event = {
      type,
      data,
      timestamp: Date.now()
    }
    this.events.push(event)
    this.sendEvent(event)
  }

  private sendEvent(event: any) {
    // 批量发送或实时发送
    if (this.events.length >= 10) {
      this.flush()
    }
  }

  flush() {
    if (this.events.length === 0) return

    fetch('/api/analytics/events', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ events: this.events })
    }).then(() => {
      this.events = []
    }).catch(console.error)
  }
}

7.2 调试工具

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// ========== Vue DevTools集成 ==========
// 开发环境启用调试功能
if (import.meta.env.DEV) {
  app.config.devtools = true

  // 添加组件名称便于调试
  app.component('UserProfile', UserProfile)
}

// ========== React DevTools ==========
// 使用React Developer Tools Profiler
import { Profiler } from 'react'

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  })
}

<Profiler id="UserProfile" onRender={onRenderCallback}>
  <UserProfile />
</Profiler>

// ========== 自定义调试工具 ==========
// utils/debug.ts
export const debug = {
  log: (...args: any[]) => {
    if (import.meta.env.DEV) {
      console.log('[DEBUG]', ...args)
    }
  },

  group: (label: string, fn: () => void) => {
    if (import.meta.env.DEV) {
      console.group(label)
      fn()
      console.groupEnd()
    }
  },

  time: (label: string) => {
    if (import.meta.env.DEV) {
      console.time(label)
    }
  },

  timeEnd: (label: string) => {
    if (import.meta.env.DEV) {
      console.timeEnd(label)
    }
  },

  track: (name: string, value: any) => {
    if (import.meta.env.DEV) {
      console.log(`[TRACK] ${name}:`, value)
    }
  }
}

// 使用示例
debug.time('dataFetch')
fetchData().then(data => {
  debug.track('fetchedData', data)
  debug.timeEnd('dataFetch')
})

总结

构建现代前端架构需要综合考虑多个维度:

  1. 架构设计:采用分层架构、领域驱动设计,确保代码可维护性
  2. 微前端:合理拆分应用,实现团队独立开发部署
  3. 状态管理:根据场景选择合适的状态管理方案
  4. 性能优化:代码分割、渲染优化、资源优化、构建优化
  5. 监控调试:建立完善的监控体系,持续优化

前端架构是一个不断演进的过程,需要根据项目实际情况灵活调整。

相关工具推荐