Files

105 lines
3.3 KiB
JavaScript
Raw Permalink Normal View History

2026-09-09 22:05:00 +08:00
/**
* Store 基类(规范 §131)
*
* 微信小程序原生开发无 Pinia/MobX,此处用极简的发布订阅实现响应式 Store:
* - state:只读数据视图
* - setState(partial):浅合并 + 通知订阅者
* - subscribe(listener):页面 onLoad/onShow 订阅,onUnload/onHide 退订
*
* 设计原则:
* 1. Store 是单例,跨页面共享。
* 2. Store 只持有"跨页面需要"的状态;纯页面局部状态仍用 this.data + setData。
* 3. Store 内部不直接操作 wx.* UI(toast/modal),UI 副作用由调用方页面承担;
* 但 AppStore 提供 toast/loading 的统一封装供页面调用。
* 4. Store 的 action 返回 Promise,错误由调用方 try/catch 并决定 UI 反馈。
*/
const { STORAGE_KEYS } = require('../constants');
class BaseStore {
constructor(initialState = {}) {
this.state = { ...initialState };
/** @type {Array<(state: object, patch: object) => void>} */
this._listeners = [];
}
/**
* 浅合并 patch 到 state,并通知所有订阅者
* @param {Partial<typeof this.state>} patch
* @returns {object} 合并后的新 state(引用变化)
*/
setState(patch) {
const next = { ...this.state, ...patch };
this.state = next;
for (const fn of this._listeners) {
try { fn(next, patch); } catch (e) { /* 单个监听者错误不影响其他 */ }
}
return next;
}
/**
* 订阅 state 变更,返回退订函数
* @param {(state: object, patch: object) => void} listener
* @returns {() => void} unsubscribe
*/
subscribe(listener) {
if (typeof listener !== 'function') return () => {};
this._listeners.push(listener);
return () => {
const i = this._listeners.indexOf(listener);
if (i >= 0) this._listeners.splice(i, 1);
};
}
/**
* 把当前 state 的指定字段同步到页面 data(页面常用辅助)
* @param {WechatMiniprogram.Page.Instance} page
* @param {{ [storeKey: string]: string }} mapping storeKey -> pageDataKey
* @returns {() => void} 退订函数
*/
bindToPage(page, mapping = {}) {
const apply = () => {
const patch = {};
for (const storeKey of Object.keys(mapping)) {
const pageKey = mapping[storeKey] || storeKey;
// 跳过 undefined 值,避免触发"Setting data field X to undefined is invalid"警告
const value = this.state[storeKey];
if (value !== undefined) {
patch[pageKey] = value;
}
}
if (Object.keys(patch).length > 0 && typeof page.setData === 'function') {
page.setData(patch);
}
};
apply(); // 立即同步一次
return this.subscribe(apply);
}
/**
* 把关键字段持久化到 wx.storage(避免刷新/冷启动丢失)
* @param {object} snapshot
* @param {string} storageKey STORAGE_KEYS 中的键
*/
persist(snapshot, storageKey) {
try {
wx.setStorageSync(storageKey, snapshot);
} catch (e) { /* ignore */ }
}
/**
* 从 wx.storage 还原(冷启动调用)
* @param {string} storageKey STORAGE_KEYS 中的键
* @returns {object|null}
*/
static hydrate(storageKey) {
try {
const v = wx.getStorageSync(storageKey);
return v && typeof v === 'object' ? v : null;
} catch (e) {
return null;
}
}
}
module.exports = BaseStore;