Angular 18中含Signal的模型序列化问题
Angular 18中含Signal的模型序列化问题
最近我在做Angular 18项目时碰到个头疼的问题:想把带Signal的模型对象序列化到localStorage或者IndexedDB里,但Signal本质上是函数,直接用JSON.stringify根本没法把Signal的值存进去。举个例子,我的模型是这样的:
// An example of the model I'm dealing with export class ExampleModel { canSerialise = ""; cantSerialise = signal(""); }
我原本想直接这么写:
// What I'd like to be able to do let exampleModel = new ExampleModel(); localStorage.setItem("example", JSON.stringify(exampleModel));
但运行后发现,cantSerialise这个Signal属性直接被忽略了——因为JSON.stringify只会序列化可枚举的普通属性,而Signal是个函数包装器,自然被跳过了。我可不想给每个带Signal的模型都手动写序列化函数,太繁琐了。
我先试了最直接的JSON.stringify:
- 直接调用JSON.stringify序列化
- 期望结果:
{"canSerialise":"","cantSerialise":X},只要X能反序列化回原来的值,并且还原成Signal就行 - 实际结果:
{"canSerialise":""},Signal属性直接丢失了
- 期望结果:
针对这个问题,我整理了几个不用手动写大量重复代码的解决方案:
方案一:用JSON.stringify的replacer自动提取Signal值
给JSON.stringify加个自定义的replacer函数,遍历对象时自动识别Signal并提取它的当前值,这个方法通用性强,所有带Signal的对象都能用:
import { isSignal } from '@angular/core'; // 自定义replacer函数,处理Signal function signalReplacer(key, value) { // 用Angular官方的isSignal工具函数判断,更准确 if (isSignal(value)) { return value(); // 提取Signal的当前值 } return value; } // 序列化使用方式 let exampleModel = new ExampleModel(); localStorage.setItem("example", JSON.stringify(exampleModel, signalReplacer)); // 反序列化时需要把值转回Signal,写个reviver函数 function signalReviver(key, value) { // 这里可以根据属性名判断,或者和后端约定哪些字段是Signal // 示例:假设我们知道ExampleModel的cantSerialise是Signal if (key === 'cantSerialise') { return signal(value); } return value; } // 反序列化示例 const storedData = localStorage.getItem("example"); const restoredModel = Object.assign(new ExampleModel(), JSON.parse(storedData, signalReviver));
方案二:给模型类添加toJSON方法
如果你的模型类数量不多,或者想让每个模型自己控制序列化逻辑,可以给类加个toJSON方法,JSON.stringify会自动调用这个方法:
import { signal, isSignal } from '@angular/core'; export class ExampleModel { canSerialise = ""; cantSerialise = signal(""); // 自定义toJSON,自动提取所有Signal值 toJSON() { const serialised = {}; for (const key in this) { if (Object.prototype.hasOwnProperty.call(this, key)) { const value = this[key]; serialised[key] = isSignal(value) ? value() : value; } } return serialised; } // 配套的fromJSON静态方法,方便反序列化还原Signal static fromJSON(json: Partial<ExampleModel>) { const model = new ExampleModel(); for (const key in json) { if (Object.prototype.hasOwnProperty.call(json, key)) { const value = json[key]; // 判断该属性原本是不是Signal if (key === 'cantSerialise') { model[key] = signal(value); } else { model[key] = value; } } } return model; } } // 序列化使用方式 let exampleModel = new ExampleModel(); localStorage.setItem("example", JSON.stringify(exampleModel)); // 反序列化使用方式 const storedData = localStorage.getItem("example"); const restoredModel = ExampleModel.fromJSON(JSON.parse(storedData));
方案三:用装饰器批量处理Signal属性
如果项目里有大量带Signal的模型,推荐用装饰器来批量处理,一劳永逸,不用给每个模型都写重复代码:
import { signal, isSignal } from '@angular/core'; // 装饰器:标记哪些属性是Signal function SignalProperty() { return function(target: any, propertyKey: string) { // 把Signal属性名存到类的静态属性里,方便后续处理 if (!target.constructor._signalProperties) { target.constructor._signalProperties = []; } target.constructor._signalProperties.push(propertyKey); }; } // 装饰器:给模型类自动添加序列化反序列化方法 function SerializableModel() { return function(constructor: Function) { // 添加toJSON方法 constructor.prototype.toJSON = function() { const serialised = {}; const signalProps = constructor._signalProperties || []; for (const key in this) { if (Object.prototype.hasOwnProperty.call(this, key)) { const value = this[key]; serialised[key] = signalProps.includes(key) && isSignal(value) ? value() : value; } } return serialised; }; // 添加fromJSON静态方法 constructor['fromJSON'] = function(json: any) { const model = new constructor(); const signalProps = constructor._signalProperties || []; for (const key in json) { if (Object.prototype.hasOwnProperty.call(json, key)) { const value = json[key]; if (signalProps.includes(key)) { model[key] = signal(value); } else { model[key] = value; } } } return model; }; }; } // 使用装饰器改造模型类 @SerializableModel() export class ExampleModel { canSerialise = ""; @SignalProperty() cantSerialise = signal(""); } // 序列化和反序列化使用起来超简单 let exampleModel = new ExampleModel(); localStorage.setItem("example", JSON.stringify(exampleModel)); const restoredModel = ExampleModel.fromJSON(JSON.parse(localStorage.getItem("example")));
需要注意的是,反序列化时一定要把存储的普通值转回Signal,不然模型的响应式能力就没了。上面的每个方案都考虑了反序列化的步骤,你可以根据项目的规模和复杂度选择最适合的方案~
备注:内容来源于stack exchange,提问作者Jarno
相关产品推荐
相关产品推荐

