你写过小程序,也一定遇到过这样的情况:页面上一个按钮点了没反应,或者列表刷新后数据错位,甚至用户刚填的表单一跳转就丢了——不是逻辑写错了,是数据没管好。
数据层不是‘加个 store’就完事
很多开发者一听说‘数据层’,第一反应就是装个 pinia 或者自己写个全局 globalData 对象。但小程序环境和 Web 不同:页面独立生命周期、setData 有性能限制、App 实例和 Page 实例之间通信路径有限。硬套 Web 那套,反而容易把数据绕晕。
轻量级数据层怎么搭?
我们试过几种方案,最后在几个中型项目里稳定跑下来的,是一个‘三层结构’:状态容器 + 业务模型 + 页面绑定器。
比如管理用户登录态:
class UserStore {
constructor() {
this._data = {
userInfo: null,
token: wx.getStorageSync('token') || ''
};
}
setUserInfo(info) {
this._data.userInfo = info;
this.notify(); // 主动通知监听页
}
notify() {
const pages = getCurrentPages();
pages.forEach(page => {
if (page.storeListeners && page.storeListeners.user) {
page.setData({ userInfo: this._data.userInfo });
}
});
}
}这个 UserStore 不依赖任何框架,只靠小程序原生 API 和页面数组就能触发更新。关键是它不强制所有页面都用 setData,而是由页面自己决定要不要监听、监听哪些字段。
页面里怎么接?
在 page 的 onLoad 里注册监听即可:
Page({
data: { userInfo: null },
onLoad() {
this.storeListeners = { user: true }; // 告诉 store:我要听用户信息
},
onUnload() {
this.storeListeners = null; // 离开时取消监听,避免内存泄漏
}
不需要引入额外库,也不用改构建配置,适合从旧项目逐步改造。
再比如商品详情页常要缓存接口结果,又得支持下拉刷新。这时候光靠 setData 不够,得结合本地缓存 + 时间戳校验:
const CACHE_KEY = 'product_detail_123';
const CACHE_TTL = 5 * 60 * 1000; // 5分钟
async function getProduct(id) {
const cached = wx.getStorageSync(CACHE_KEY);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const res = await wx.request({ url: `/api/product/${id}` });
wx.setStorageSync(CACHE_KEY, {
data: res.data,
timestamp: Date.now()
});
return res.data;
}这种写法看着朴素,但在真实业务里比“全量重刷 state”更稳、更省流量。
别忘了离线场景
地铁进隧道、电梯里信号弱——小程序照样得能显示上次加载的内容。数据层如果没设计缓存兜底,用户看到的就是一片白屏或 loading 转圈。我们在购物车模块里加了一层内存+本地双缓存:页面加载时先读内存(快),内存没有再读本地(稍慢但可靠),最后才发网络请求。用户点加入购物车,立刻更新 UI,同时异步同步到服务端,失败了就留在本地等下次联网自动重试。
数据层不是越重越好,而是越贴业务越活。一个能随时被删掉、替换、局部启用的方案,才是真接地气的小程序数据层。