多端架构最难的不是 UI,而是这 3 个致命陷阱

0 阅读5分钟

🚀 多端架构最难的不是 UI,而是这 3 个致命陷阱

在前两篇中,我们完成了两件看起来“很重要”的事情:

  • 第 2 篇:架构设计(UI / modules / services)
  • 第 3 篇:Monorepo 工程落地

很多人会在这个阶段产生一个错觉:

pnpm install 能跑通 = 架构已经完成

但现实是:

这只是“代码能运行”,不是“架构成立”

👉 物理上的代码共享(Code Sharing),并不等于逻辑上的跨端通用。

真正的架构深水区,才刚刚开始。


🧠 一、问题的本质:我们到底在共享什么?

很多人做多端,第一反应是:

UI 怎么统一?

但在真实项目中你会发现:

UI 反而是最容易解决的

真正的崩溃场景是:

  • 在小程序里运行包含 window.location 的逻辑 ❌
  • 在 Web 端处理不了小程序登录态 ❌

👉 本质问题是:

业务逻辑无法脱离平台独立运行

👉 所以跨端的核心不是:

“怎么写兼容代码”

而是:

“如何让核心逻辑感知不到平台的存在”

🔥 二、陷阱 1:Request 层的“名义复用”


❗ 问题现象

在 Web 中:

axios.get('/api/user')

在小程序中:

wx.request({ url: '/api/user' })

如果你在 services 层这样写:

// ❌ 无法跨端复用
export const getUserInfo = () => axios.get('/api/user')

👉 结果是:

业务逻辑被平台 API “污染”

🔥 本质问题

请求能力没有被抽象,只是被“换了个写法”

✅ 解决方案:Adapter Pattern(适配器模式)

不仅仅是封装 URL,而是:

统一请求协议 + 抹平响应结构 + 收敛拦截逻辑

📦 推荐结构

packages/request/
├── core/
├── adapters/
│   ├── web.ts
│   ├── mini.ts

💻 核心实现

// core/types.ts
export interface UnifiedResponse<T> {
  code: number
  data: T
  msg: string
}

// core/request.ts
export async function request<T>(options): Promise<UnifiedResponse<T>> {
  const config = preRequest(options)
  const rawResponse = await adapter(config)
  return normalize(rawResponse)
}

🔥 关键结论

request 层的本质,是把“环境差异”收敛到一个边界内

👉 最终效果:

services 层完全变成“环境无关”的纯函数

⚠️ 三、陷阱 2:Modules 的“重力坍塌”


❗ 常见现象

随着业务复杂:

modules 变成 utils 大杂烩

或者:

组件中直接写业务逻辑

👉 最终:

复用失败 + 代码爆炸

🔥 本质问题

缺乏清晰的“领域边界”

❗ 一个典型错误

// ❌ 不可跨端
showToast('登录成功')

👉 为什么错?

因为 UI 行为被写进了业务逻辑

✅ 正确做法:Environment Agnostic(环境无关)

modules 必须是纯粹的领域层(Domain Layer)

📦 推荐结构

modules/user/
├── model.ts
├── service.ts
├── hooks.ts
├── types.ts

💻 示例

export function useUserDomain() {
  const [user, setUser] = useState(null)

  const login = async (credentials) => {
    const data = await authService.login(credentials)
    setUser(data)
  }

  return { user, login }
}

🔥 关键认知

modules 不是工具库,而是业务的“大脑”

大脑不应该知道,它是在控制网页,还是小程序

❗ 更进一步的判断标准

如果你的 modules 可以被随便 copy 到另一个项目直接使用,
那它大概率只是“工具库”,而不是“领域层”

🎨 四、陷阱 3:Design System 的“皮同肉不同”


❗ 常见误区

Design System = 组件库

👉 于是开始:

疯狂封装 Button / Input

👉 结果:

不同端体验依然割裂

🔥 本质问题

只有组件实现,没有设计规则

✅ 正确做法:三层设计系统


📦 结构

design-system/
├── tokens
├── foundations
├── patterns ⭐


🔥 最关键:patterns

例如:

列表页必须包含:
- 筛选区
- 数据流
- 空状态
- 错误态

👉 不同端实现:

实现方式
WebReact
小程序WXML

👉 但核心是:

行为一致,而不是外观一致

🔥 核心结论

Design System 的目标,不是“长得一样”,而是“行为一致”

👉 实现方式:

共享逻辑(Headless) + 各端 UI 自实现

🧠 五、总结:抽象是唯一的出路

看似是 3 个问题,本质只有一个:

抽象能力不足

🔁 抽象分层

层级解决问题手段抽象
基础层环境差异request adapterIO 抽象
业务层逻辑复用modules业务抽象
体验层交互一致design patterns体验抽象

🔥 更深一层理解

抽象的本质,不是让代码更通用
而是让变化被限制在可控范围内

👉 所以:

跨端的核心,不是技术,而是抽象能力

🚀 结语

很多人做多端,会陷入一个误区:

不断写 if (isMiniProgram)

但这本质上是:

在用补丁对抗复杂度

👉 真正的架构应该是:

让平台差异被“隔离”,而不是被“处理”

🔮 下一篇预告(完结篇)

多端架构的终局:前端平台化

👉 我会从更高视角讲清楚:

当逻辑彻底解耦后,前端如何进化为“平台能力”

🔥 这一篇,将决定你能否完成从“工程实现”到“架构思维”的跃迁。


💬 聊聊你的看法

你在多端开发中,遇到过最离谱的“平台差异坑”是什么?

欢迎在评论区分享,一起避坑 👇


#前端架构 #跨端开发 #Monorepo #React