Files

105 lines
3.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;