跨端小程序架构复盘:统一请求层、身份状态与缓存边界
本文目录
多端小程序最难的部分,往往不是把页面编译成不同目标,而是让请求、登录态、本地缓存和宿主能力在各端保持一致。本文基于一次私人多端小程序源码审阅,抽象出可复用的设计方法;不包含私有仓库地址、业务标识或真实接口路径。审阅时的依赖清单声明 Taro 4.1.11、React 18 和 Zustand 5.0.12,这只是该源码快照,不是对新项目的版本推荐。
把平台差异收敛到边界
应用用 Taro 统一页面与宿主调用,React 负责组件状态,Zustand 管理跨页面状态。小程序、H5 和其他目标的构建命令分别明确,这有利于让平台差异留在构建与适配层,而不是散落在每个页面中。
值得保留的结构是:
- 页面只调用按领域分组的 API,例如目录、商品、账户或收藏。
- 请求层统一处理 base URL、HTTP method、JSON header、认证头和失败映射。
- 按公开/需认证请求显式选择认证策略,避免公开资源意外带上用户凭据。
- 宿主存储、导航栏颜色和 H5 CSS 变量通过小型适配函数收口。
- 面向多个构建目标的配置走环境变量;本机开发地址不能成为正式构建的隐式默认值。
Taro 的 request 负责发起跨端 HTTP 请求,具体参数转换和目标平台支持应以当前使用版本的 API 文档与开发者工具为准。构建成功只证明 bundler 能生成产物,不代表小程序后台已经配置 request 合法域名,也不证明真机网络、H5 代理和线上 HTTPS 证书都正常。
请求层:统一行为,也要保留可取消能力
一个小而明确的请求封装通常比页面内重复拼接请求更可靠:
type RequestOptions<T> = {
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
data?: T
auth?: 'required' | 'optional' | 'none'
timeoutMs?: number
}
async function request<TResponse, TBody = unknown>(
path: string,
options: RequestOptions<TBody> = {},
): Promise<TResponse> {
const method = options.method ?? 'GET'
const authMode = options.auth ?? 'required'
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (authMode !== 'none') {
const token = readTokenSafely()
if (token) headers.Authorization = `Bearer ${token}`
}
return platformRequest<TResponse>({
url: resolveApiUrl(path),
method,
data: options.data,
header: headers,
timeout: options.timeoutMs ?? 15_000,
})
}
示例中的 platformRequest 和 readTokenSafely 是项目自己的适配接口,不是 Taro 内置函数。真实实现还要定义服务端 envelope、业务码和 HTTP 状态码的映射;2xx 并不一定代表业务成功。认证失败时,拦截层应发出统一的 session-expired 事件,让状态 store 清空内存态与持久化态,再由页面展示重新登录入口。
跨端请求最好提供超时和取消机制。Taro 请求可以返回任务句柄,不同平台的取消行为需要实测;若 API 只返回 Promise,搜索页面快速切换关键词时就难以中止旧请求,迟到的响应可能覆盖新结果。可以在适配层将请求 Promise 与平台 task 一起返回,或者在页面用请求序号忽略过期响应。无论选择哪种方式,读写操作都应按业务需要增加幂等键和重复提交保护。
查询参数要有确定规则
常见 query builder 只对键和值做 encodeURIComponent(String(value)),适合简单标量,不足以表达数组、对象、布尔值和嵌套筛选。先定义 API 契约,再统一序列化:
function appendQuery(url: URL, params: Record<string, unknown>) {
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null || value === '') continue
if (Array.isArray(value)) {
for (const item of value) url.searchParams.append(key, String(item))
continue
}
if (typeof value === 'object') {
throw new TypeError(`Query field "${key}" must be encoded by its API contract`)
}
url.searchParams.set(key, String(value))
}
}
不要把任意对象默认 String() 成 [object Object];也不要在没有约定时随意 JSON.stringify。后端解析方式、重复键语义和数组格式要一起写进接口契约和测试。
登录态:持久化前先做安全投影
客户端状态需要区分服务端身份事实、令牌和展示资料:
- access token 单独保存到平台安全性允许的存储位置,并设置服务端过期与撤销策略。
- 客户端用户对象用显式白名单构建,只保留 UI 必需字段;类型定义不应包含密码字段。
- 游客状态和已认证状态分开建模;游客不应被误判为已登录账户。
- 登出、401、账号切换和封禁响应都必须清理内存 state、持久化用户资料、令牌以及该账户的缓存。
- 后端对每个受保护请求重新确认账号状态和权限。客户端 role 只用于界面展示,不能作为授权依据。
有一个很容易漏掉的状态一致性问题:请求拦截器只删掉持久化令牌,却没有通知 Zustand store 时,页面内存仍可能认为用户已登录。刷新页面后状态变化,说明单独清 token 不能完成 logout。应提供一个统一的失效动作,并同时清理状态、缓存和正在进行的用户请求。
本地缓存要按身份隔离
离线体验和快速首屏可以把用户偏好、收藏或最近浏览写到本地,但 key 与合并规则必须体现账户边界。一个全局 favorites key 在共享设备登录不同账户时可能串数据。推荐的基本结构是:
function favoriteStorageKey(accountId: string) {
return `reader:v1:${encodeURIComponent(accountId)}:favorites`
}
本地 store 先乐观更新时,应记录待同步变更并在失败时回滚,或者显示“仅保存在本机”的状态。服务端返回空集合代表权威状态为空,不能因为数组长度是 0 就跳过同步、继续保留旧账号的收藏。退出账号时按策略清理或冻结账户命名空间,并在下个账号登录后加载自己的数据。
多端冲突还需定义时间戳、版本号、服务端主导或字段级合并规则。仅比较本机时间不足以解决时钟偏差;重要数据以服务端为准,本地数据应可重建、可删除并且不包含支付凭据或不必要的个人信息。
主题状态是跨宿主适配问题
主题 store 保存当前颜色配置,并同时更新小程序 TabBar 与 H5 CSS custom properties,是一种清晰的“状态层调用宿主适配层”模式。但如果刷新后应保留用户选择,还需要显式持久化主题 key,并对无效值回退默认主题。直接保存任意 CSS 字符串会扩大注入面,建议只接受枚举主题,再从本地固定映射生成颜色和渐变。
主题切换至少要测试:冷启动默认值、热启动恢复、未知主题回退、小程序 TabBar 更新失败、H5 初始闪烁和减少动画偏好。宿主 API 可能在当前页面或平台不可用,降级只能影响装饰层,不能让核心导航或数据请求失败。
源码复盘时的风险清单
一次完整审阅应逐条确认以下边界,并在实际仓库写回回归测试:
- 401 后 store、令牌和账户缓存是否同步失效。
- 多账户共用设备时,本地收藏、购物车和浏览历史是否隔离。
- 空的服务端集合能否清除过期本地快照。
- 快速搜索时,迟到响应是否可能覆盖新查询结果。
- GET 参数对数组、布尔值和特殊字符的编码是否与服务端一致。
- 请求超时、取消、重试和重复提交是否有清晰策略。
- 正式构建是否拒绝 localhost API 地址,并检查每个小程序平台的合法域名与 HTTPS 要求。
- 类型断言是否掩盖服务端数据结构变化;网络 JSON 应经运行时 schema 校验后再进入核心 store。
- 游客和正式账户的数据合并、登出清理以及重新登录恢复是否有测试。
- 应用日志和错误上报是否过滤令牌、密码、手机号及完整用户资料。
建议的回归矩阵
| 验证面 | 需要覆盖 |
|---|---|
| 目标构建 | 微信小程序、H5,以及项目声明支持的其他端分别构建 |
| 认证状态 | 登录、注册、游客、401、封禁、登出、账号切换 |
| 本地存储 | 空值、损坏 JSON、配额异常、版本迁移和不同账户隔离 |
| 网络行为 | 慢响应、超时、取消、离线、重复点击和错误 envelope |
| 主题能力 | 平台 API 缺失、H5 刷新、无效颜色 key 和系统主题切换 |
| 发布环境 | 构建环境变量、合法 request 域名、HTTPS 与服务端 CORS/鉴权 |
资料
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。