架构设计的本质是管理复杂度。没有特效药,只有权衡。
前言
在多年的技术实践中,我逐渐意识到一个真相:优秀的架构不是设计出来的,而是在不断重构中生长出来的。本文将结合一个真实的项目,分享在架构设计上的思考与实践。
一、配置驱动的组件系统
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
}
结语
架构设计的本质是管理复杂度 。没有特效药,只有权衡。
几点核心感悟:
- 简单优于复杂:不要过度设计,先让代码工作
- 演进优于预测 :为变化而设计,而不是为未来设计
- 约束优于自由:建立规范,减少选择
- 可读性优于性能:大多数场景下,可维护性更重要
软件架构不单纯是关于技术选型,而是在于如何在变化中保持系统的稳定与优雅。
架构师的职责不是追求完美的技术方案,而是在业务需求、团队能力、时间成本之间找到最佳平衡点。
好的架构师是园丁,而不是一位建筑师。园丁创造条件让系统自然生长,建筑师试图控制每一个细节。