架构艺术,是权衡的艺术。

架构设计的本质是管理复杂度。没有特效药,只有权衡

前言

在多年的技术实践中,我逐渐意识到一个真相:优秀的架构不是设计出来的,而是在不断重构中生长出来的。本文将结合一个真实的项目,分享在架构设计上的思考与实践。

一、配置驱动的组件系统

1.1 问题的提出

在复杂的业务场景中,我们经常面临这样的困境:

  • 页面结构需要动态调整
  • 不同客户需要定制化UI
  • 新的组件类型需要快速接入

传统的硬编码方式会导致代码臃肿,可维护性急剧下降。

1.2 架构方案

我采用了一种配置驱动的架构模式:

javascript 复制代码
// 核心思想:通过配置key映射到具体组件
const componentMap = {
  'graphicnotice': 'DiyNotice',
  'graphicweathernotice': 'WeatherAnnouncement',
  'graphicfind': 'DiySearch',
  'graphicswiper': 'DiySwiper',
  // ... 更多组件
}

在模板层面,使用策略模式进行组件分发:

vue 复制代码
<template>
  <view>
    <block v-for="(item, index) in diyItems" :key="index">
      <!-- 公告组 -->
      <diy-notice v-if="item.key === 'graphicnotice'"
        :item-style="item.style"
        :params="item.params" />

      <!-- 搜索框 -->
      <diy-search v-else-if="item.key === 'graphicfind'"
        :item-style="item.style"
        :params="item.params"
        :hot-data="item.data" />

      <!-- 图片轮播 -->
      <diy-swiper v-else-if="item.key === 'graphicswiper'"
        :item-style="item.style"
        :item-data="item.data" />

      <!-- 动态组件方案(更优雅) -->
      <component v-else :is="getComponent(item.key)"
        v-bind="getComponentProps(item)" />
    </block>
  </view>
</template>

1.3 设计权衡

显式if-else vs 动态component

方案 优点 缺点
显式if-else 类型安全、IDE支持好、易于调试 代码冗长、扩展需修改模板
动态component 代码简洁、易于扩展 类型检查弱、调试困难

我的建议:混合使用。对于核心业务组件使用显式声明,对于通用展示组件使用动态方案。

二、状态管理的演进

2.1 从全局到局部

早期项目往往陷入"全局状态陷阱":

javascript 复制代码
// 错误示范:一切皆全局
Vue.prototype.$globalData = {
  user: null,
  cart: [],
  orders: [],
  settings: {}
}

2.2 分层状态设计

我推荐的状态管理分层:

scss 复制代码
┌─────────────────────────────────────┐
│         全局状态 (Vuex/Pinia)        │
│   用户信息、认证状态、全局配置         │
├─────────────────────────────────────┤
│         模块状态 (Composables)       │
│   业务逻辑、数据获取、缓存策略         │
├─────────────────────────────────────┤
│         组件状态 (ref/reactive)      │
│   UI状态、临时数据、交互逻辑          │
└─────────────────────────────────────┘

2.3 组合式API的状态封装

javascript 复制代码
// composables/useGoods.js
export function useGoods(categoryId) {
  const goodsList = ref([])
  const loading = ref(false)
  const error = ref(null)

  const fetchGoods = async () => {
    loading.value = true
    try {
      const { data } = await api.getGoods(categoryId)
      goodsList.value = data
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  // 自动请求
  onMounted(fetchGoods)

  // 提供刷新能力
  const refresh = () => fetchGoods()

  return {
    goodsList: readonly(goodsList),
    loading: readonly(loading),
    error: readonly(error),
    refresh
  }
}

三、组件设计的原则

3.1 单一职责原则

一个组件应该只有一个变化的原因:

javascript 复制代码
// 反模式:上帝组件
export default {
  data() {
    return {
      userInfo: {},
      orderList: [],
      goodsList: [],
      settings: {},
      // ... 几十个状态
    }
  },
  methods: {
    fetchUser() {},
    fetchOrders() {},
    fetchGoods() {},
    updateSettings() {},
    // ... 几十个方法
  }
}

// 正确:职责分离
// UserProfile.vue - 只负责用户信息展示
// OrderList.vue - 只负责订单列表
// GoodsGrid.vue - 只负责商品网格

3.2 Props向下,Events向上

vue 复制代码
<!-- 子组件:纯展示,无副作用 -->
<template>
  <view class="goods-card" @click="$emit('select', goods.id)">
    <image :src="goods.image" />
    <text>{{ goods.name }}</text>
    <text class="price">¥{{ goods.price }}</text>
  </view>
</template>

<script>
export default {
  props: {
    goods: {
      type: Object,
      required: true
    }
  },
  emits: ['select']
}
</script>

3.3 组件通讯的最佳实践

javascript 复制代码
// 1. 简单父子:Props/Events
// 2. 共享状态:Composables
// 3. 跨层级:Provide/Inject
// 4. 全局事件:EventBus(谨慎使用)

// 推荐:Composables模式
// shared/useCart.js
export const cartState = reactive({
  items: [],
  total: 0
})

export function useCart() {
  const addItem = (goods) => { /* ... */ }
  const removeItem = (id) => { /* ... */ }

  return {
    cart: readonly(cartState),
    addItem,
    removeItem
  }
}

四、错误处理的架构

4.1 错误边界设计

javascript 复制代码
// utils/errorHandler.js
export class AppError extends Error {
  constructor(message, code, details) {
    super(message)
    this.code = code
    this.details = details
    this.timestamp = Date.now()
  }
}

// 全局错误处理
export function setupErrorHandler(app) {
  app.config.errorHandler = (err, vm, info) => {
    // 1. 记录错误
    console.error('Global error:', err)

    // 2. 上报监控
    reportError({
      message: err.message,
      stack: err.stack,
      component: vm?.$options?.name,
      info
    })

    // 3. 用户友好提示
    uni.showToast({
      title: '系统繁忙,请稍后重试',
      icon: 'none'
    })
  }
}

4.2 API错误处理

javascript 复制代码
// utils/request.js
export async function request(url, options = {}) {
  try {
    const response = await uni.request({
      url: `${BASE_URL}${url}`,
      ...options
    })

    if (response.statusCode === 401) {
      // 统一处理认证过期
      await handleAuthError()
      throw new AppError('认证过期', 'AUTH_EXPIRED')
    }

    if (response.statusCode >= 400) {
      throw new AppError(
        response.data?.message || '请求失败',
        'API_ERROR',
        response.data
      )
    }

    return response.data

  } catch (error) {
    // 网络错误
    if (error.errMsg?.includes('timeout')) {
      throw new AppError('网络超时', 'TIMEOUT')
    }
    throw error
  }
}

五、性能优化的架构思维

5.1 懒加载策略

javascript 复制代码
// 路由级懒加载
const routes = [
  {
    path: '/goods/:id',
    component: () => import('@/pages/goods/Detail.vue')
  }
]

// 组件级懒加载
const AsyncComponent = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,
  timeout: 3000
})

// 数据级懒加载
function useLazyData(fetchFn) {
  const data = ref(null)
  const loaded = ref(false)

  const load = async () => {
    if (loaded.value) return data.value
    data.value = await fetchFn()
    loaded.value = true
    return data.value
  }

  return { data, load }
}

5.2 缓存架构

javascript 复制代码
// 多级缓存策略
class CacheManager {
  constructor() {
    this.memoryCache = new Map()
    this.storagePrefix = 'app_cache_'
  }

  async get(key, fetchFn, options = {}) {
    const { ttl = 300000, level = 'memory' } = options

    // 1. 内存缓存
    if (this.memoryCache.has(key)) {
      const cached = this.memoryCache.get(key)
      if (Date.now() - cached.timestamp < ttl) {
        return cached.data
      }
    }

    // 2. 持久化缓存
    if (level === 'storage') {
      const stored = uni.getStorageSync(`${this.storagePrefix}${key}`)
      if (stored && Date.now() - stored.timestamp < ttl) {
        this.memoryCache.set(key, stored)
        return stored.data
      }
    }

    // 3. 远程获取
    const data = await fetchFn()
    const cacheEntry = { data, timestamp: Date.now() }

    this.memoryCache.set(key, cacheEntry)
    if (level === 'storage') {
      uni.setStorageSync(`${this.storagePrefix}${key}`, cacheEntry)
    }

    return data
  }

  invalidate(key) {
    this.memoryCache.delete(key)
    uni.removeStorageSync(`${this.storagePrefix}${key}`)
  }
}

六、可测试性设计

6.1 依赖注入

javascript 复制代码
// 不好的设计:硬编码依赖
export default {
  methods: {
    async fetchUser() {
      const res = await uni.request({ url: '/api/user' })
      return res.data
    }
  }
}

// 好的设计:依赖注入
export default {
  inject: ['userService'],
  methods: {
    async fetchUser() {
      return await this.userService.getUser()
    }
  }
}

6.2 纯函数业务逻辑

javascript 复制代码
// 业务逻辑与UI分离
// utils/orderCalculator.js
export function calculateOrderTotal(items, coupons) {
  const subtotal = items.reduce((sum, item) =>
    sum + item.price * item.quantity, 0)

  const discount = coupons.reduce((sum, coupon) =>
    sum + calculateCouponDiscount(coupon, subtotal), 0)

  const shipping = subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : SHIPPING_FEE

  return {
    subtotal,
    discount,
    shipping,
    total: Math.max(0, subtotal - discount + shipping)
  }
}

// 纯函数,易于测试
describe('calculateOrderTotal', () => {
  it('should apply coupon discount correctly', () => {
    const items = [{ price: 100, quantity: 2 }]
    const coupons = [{ type: 'fixed', value: 30 }]

    const result = calculateOrderTotal(items, coupons)
    expect(result.total).toBe(170)
  })
})

七、架构演进的策略

7.1 渐进式重构

复制代码
阶段一:识别痛点
├── 代码重复
├── 复杂度高
└── 变更成本大

阶段二:提取抽象
├── 共用逻辑 → Composables
├── 共用UI → 通用组件
└── 共用配置 → 常量/枚举

阶段三:建立规范
├── 命名规范
├── 文件结构
└── 代码风格

阶段四:持续优化
├── 性能监控
├── 错误追踪
└── 代码审查

7.2 技术债务管理

债务项 优先级 影响范围 预计成本 计划时间
重构订单模块 核心业务 15天 Q3
升级Vue版本 全局 10天 Q4
添加单元测试 工具链 持续 持续

八、实战案例:DIY页面系统

回到我们的核心案例,这套架构的关键设计决策:

8.1 数据驱动的UI

javascript 复制代码
// 页面配置数据结构
{
  key: 'graphicsearch',     // 组件类型标识
  style: {                  // 样式配置
    background: '#fff',
    borderRadius: '8px'
  },
  params: {                 // 功能参数
    placeholder: '搜索商品',
    hotWords: ['热销', '新品']
  },
  data: []                  // 业务数据
}

8.2 扩展性设计

javascript 复制代码
// 新增组件只需3步:
// 1. 创建组件文件
// 2. 注册到DiyPage
// 3. 添加key映射

// 组件注册
components: {
  DiyNotice,
  DiySearch,
  DiySwiper,
  NewComponent  // 新增
}

// 模板映射
<new-component v-if="item.key === 'newkey'" />

8.3 性能优化

javascript 复制代码
// 1. 组件懒加载
const DiyVideo = () => import('./Video.vue')

// 2. 列表虚拟滚动
<virtual-list :data="goodsList" :item-height="120" />

// 3. 图片懒加载
<image lazy-load :src="item.image" />

// 4. 请求去重
const requestCache = new Map()
async function fetchWithCache(url) {
  if (requestCache.has(url)) {
    return requestCache.get(url)
  }
  const promise = request(url)
  requestCache.set(url, promise)
  return promise
}

结语

架构设计的本质是管理复杂度 。没有特效药,只有权衡

几点核心感悟:

  1. 简单优于复杂:不要过度设计,先让代码工作
  2. 演进优于预测 :为变化而设计,而不是为未来设计
  3. 约束优于自由:建立规范,减少选择
  4. 可读性优于性能:大多数场景下,可维护性更重要

软件架构不单纯是关于技术选型,而是在于如何在变化中保持系统的稳定与优雅。

架构师的职责不是追求完美的技术方案,而是在业务需求、团队能力、时间成本之间找到最佳平衡点。

好的架构师是园丁,而不是一位建筑师。园丁创造条件让系统自然生长,建筑师试图控制每一个细节。


相关推荐
偷偷挖矿的牛马矿工1 小时前
execa NodeJs新一代child_process的强力替代品
javascript
YIAN1 小时前
ESLint 新版 Flat Config 实战:团队代码风格统一,从这一份配置开始
前端·团队管理
计算机魔术师1 小时前
英伟达单季营收逼近千亿美元,黄仁勋放话:真实需求远超想象
前端
明月_清风1 小时前
看完这段关于"全插件化架构"的技术分析后,我整理了一份笔记
前端·后端
YIAN1 小时前
Next.js App Router 全栈实战:从 0 到 1 写一个 Todo 应用,前端后端一个项目搞定
前端·全栈·next.js
计算机魔术师1 小时前
英伟达预计 2028 财年营收同比增 70%,黄仁勋称实际需求远高于此
前端
掘金酱1 小时前
🔥 AI 时代,Token 就是你的数字燃料!晒账单,赢好礼!
前端·人工智能·ai编程
计算机魔术师1 小时前
Warp用Claude搭自我改进智能体
前端
计算机魔术师2 小时前
英伟达Q2营收翻倍,黄仁勋称实际需求远超70%指引
前端