105 lines
3.3 KiB
JavaScript
105 lines
3.3 KiB
JavaScript
/**
|
||||
|
|
* 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;
|