
本文介绍如何将基于 SignalStory 的类式状态管理(如 RegistrationStore)重构为纯函数式风格,包括构造函数替代、原型链模拟、继承关系还原及 Store 初始化逻辑的函数化封装。
本文介绍如何将基于 signalstory 的类式状态管理(如 registrationstore)重构为纯函数式风格,包括构造函数替代、原型链模拟、继承关系还原及 store 初始化逻辑的函数化封装。
在现代前端开发中,尤其使用 SignalStory 这类基于 Immer/produce 的响应式状态库时,开发者常依赖 class 组织领域模型(如 Registration、RegistrationState)与 Store(如 RegistrationStore)。但有时出于测试简化、Tree-shaking 优化或函数式编程范式统一的需求,需将类结构转为函数式实现。注意:这不是简单地用工厂函数替代 new,而是完整模拟类的构造、原型继承、实例初始化及 super() 调用语义。
✅ 核心原则:类即函数,继承即原型链委托
JavaScript 中 class 本质是语法糖,其底层仍基于函数与原型链。extends 的关键在于:
- 子类构造函数必须通过
super()调用父类构造器; - 实例需正确继承父类原型方法与私有字段(若存在);
-
Reflect.construct()是模拟new+super()的标准方式。
以下以 RegistrationStore 为例,分步重构:
1. 将 RegistrationState 类转为函数构造器(含 super 语义)
原类:
export class RegistrationState {
id: TId = nanoid();
domain: TRegistration = new Registration();
ui: TRegistrationUIState = new RegistrationUiState();
}函数式等价实现(无需继承,直接初始化):
export const createRegistrationState = (): TRegistrationState => ({
id: nanoid(),
domain: { id: nanoid(), mrn: 0, vn: 0 },
ui: { id: nanoid(), isSelected: false }
});✅ 优势:无
this绑定风险,纯数据生成,易于单元测试与序列化。
2. 模拟 ImmutableStore<t></t> 继承(关键!)
RegistrationStore 继承自 ImmutableStore<tregistrationstate></tregistrationstate>,需保留 super() 行为。使用 Reflect.construct 安全调用父类构造器:
import { ImmutableStore, produce } from 'signalstory';
import { useStorePersistence } from 'signalstory/plugins';
// 假设环境变量已定义
const environment = { production: false, defaultStorePlugins: () => [] };
// 函数式 RegistrationStore 构造器
export const createRegistrationStore = () => {
// 1. 手动构建父类实例(模拟 super(...))
const storeInstance = Reflect.construct(
ImmutableStore,
[{
name: 'RegistrationStore',
mutationProducerFn: produce as any,
initialState: createRegistrationState(),
plugins: [
useStorePersistence({
persistenceKey: 'RegistrationStore',
persistenceStorage: localStorage,
}),
...environment.defaultStorePlugins(),
],
}],
RegistrationStore // new.target 确保原型链正确
);
// 2. 添加实例属性(等价于 class 中的 this.store = this.state())
Object.defineProperty(storeInstance, 'store', {
get() {
return this.state(); // 注意:需确保 state() 方法在父类原型上可用
},
enumerable: true,
});
return storeInstance;
};
// 使用方式(仍需 new,因 ImmutableStore 是类)
const registrationStore = new (createRegistrationStore())();3. 更彻底的函数式方案:放弃 new,返回配置对象
若目标是完全消除 class 和 new,可进一步封装为工厂函数,返回包含所有行为的对象:
export const buildRegistrationStore = () => {
const initialState = createRegistrationState();
// 模拟 store.state() —— 返回当前不可变快照
let currentState = initialState;
// 模拟 mutation(简化版,实际应集成 produce)
const mutate = (fn: (draft: TRegistrationState) => void) => {
const draft = structuredClone(currentState); // 或用 immer produce
fn(draft);
currentState = draft;
};
return {
state: () => currentState as const,
mutate,
// 可扩展 persist、reset 等方法...
};
};
// 使用
const store = buildRegistrationStore();
console.log(store.state());
store.mutate(draft => { draft.domain.mrn = 12345; });⚠️ 注意事项与最佳实践
-
Reflect.construct是关键:它能正确处理super()调用和new.target,比手动Object.create(Proto).constructor(...)更可靠; -
避免箭头函数作为构造器:箭头函数无
prototype和arguments,无法被new调用; -
SignalStory 插件兼容性:
useStorePersistence等插件内部可能依赖this.constructor.name或instanceof,函数式实例需确保原型链不被破坏; -
类型安全:TypeScript 中函数构造器需显式声明返回类型,例如
function RegistrationStore(): InstanceType<typeof immutablestore>></typeof>; -
推荐渐进式迁移:优先将领域模型(
Registration,RegistrationState)转为工厂函数;Store 层若重度依赖 SignalStory 内部机制,可暂保留class,仅将初始化逻辑提取为函数。
最终,函数式重构不是为了炫技,而是提升可预测性、可组合性与测试友好性。只要抓住“构造即初始化、继承即原型委托、状态即不可变值”三大本质,即可平稳过渡。

















