同一产品需要覆盖 PC、App 内 H5 和小程序时,“一套代码多端运行”听起来是最自然的目标。实际开发中,各端的路由、生命周期、权限、存储和交互习惯都不同。强行抹平差异,会把平台判断散落到业务代码里。
复用之前,应该先明确哪些层不应复用。视图和平台能力往往变化最快,领域规则、接口模型和数据转换更稳定。
用适配层隔离平台
相机、分享、登录和返回行为可以通过小而明确的适配接口暴露。业务代码依赖“选择图片”或“获得身份”,而不是直接判断当前是否在微信或 App WebView。
适配层不能假装所有平台能力完全一致。某端不支持的能力应该明确返回不可用,让产品决定降级方式,而不是静默失败。
例如选择图片不应该返回一个各端都含义不同的字符串:
type PickedImage = {
localUri: string;
mimeType?: string;
sizeBytes?: number;
};
type CapabilityResult<T> =
| { ok: true; value: T }
| { ok: false; reason: "unsupported" | "denied" | "cancelled" | "failed" };
interface MediaCapability {
pickImage(options: { maxCount: number }): Promise<CapabilityResult<PickedImage[]>>;
}小程序返回临时路径,Web 返回 File,App Bridge 可能先返回资产 ID。适配器负责把这些结果变成业务可理解的最小结构;如果上传仍需平台对象,则把上传也留在适配器内,不要泄漏半转换状态。
复用要计算维护成本
共享一段代码节省了首次开发,却可能增加测试组合和发布耦合。如果两个端的迭代节奏不同,过度共享会让小改动触发全端回归。
我更愿意复用稳定规则,而不是追求文件层面的统一。允许少量视图重复,换取清晰的平台边界,通常比一个充满条件分支的“万能组件”更便宜。
测试矩阵要跟着边界走
跨端项目的测试数量很容易相乘。所有功能在所有平台、所有版本上完整回归并不现实。我们按共享层和平台层设计测试:领域规则运行一套稳定用例,适配器针对每个平台验证契约,关键用户路径再覆盖真实设备组合。
| 层 | Web | App H5 | 小程序 |
|---|---|---|---|
| 领域规则 | 同一套单元测试 | 同一套 | 同一套 |
| 能力适配器 | 浏览器权限与 File | Bridge 版本与回调 | 授权弹窗与临时路径 |
| 关键路径 | 主流浏览器 | 最低支持 App 版本 | 当前与上一基础库版本 |
这样新增一个业务校验不需要全端重复验证,修改 Bridge 协议却会明确触发 App H5 的兼容回归。测试数量跟随边界,而不是跟随仓库文件数。
发布也要记录各端版本对应的协议能力。H5 可以快速更新,App 与小程序存在审核和用户升级延迟,前端不能只按照最新客户端假设。兼容范围如果没有被写出来,就会以线上偶发错误的方式出现。
跨端架构的目标不是最高复用率,而是让差异可见、可控制。承认平台不同,才有可能真正共享值得共享的部分。
图:共享的是业务语义和协议,不是强行共享所有实现。
先做能力矩阵,再讨论复用比例
我们会把登录、返回、分享、上传、支付和文件预览列成矩阵,逐端写输入、输出、权限、超时和降级。只有语义一致的能力共享接口,纯视觉和平台特有体验允许分开。
| 能力 | Web | App WebView | 小程序 |
|---|---|---|---|
| 登录 | Cookie 会话 | Bridge 换短期票 | 平台 code 换会话 |
| 分享 | Web Share/复制 | 原生分享面板 | 平台分享钩子 |
| 失败 | 页面提示 | Bridge 错误码 | 平台回调 |
这张矩阵比“复用 80%”更能预测真实成本。