如何将原型分配给泛型类型



我试图建立一个sessionStorage服务,可以解析对象字符串到泛型类型。但是这个实现只返回一个没有原型的对象(没有对象函数)。

下面是代码:
public static getObject<T>(key: string): T{
return JSON.parse(sessionStorage.getItem(key)) as T;
}

问候!

JSON不编码函数、原型或构造函数。解析JSON永远不会为您创建任何类的实例。它只是原始的结构化数据。这对于大多数序列化javascript数据的方式都是正确的。

没有什么好办法。这取决于你如何构建你的代码库,这样事情就会在头脑中工作。

对此有无数种解决方案,但例如,您可以在获取该数据后将其传递给构造函数。

像这样:

const data = sessionStorageInstance.getObject<MyInterfaceHere>('some/key')
const instance = new MyClassHere(data)

我假设您使用的代码有点像这样?

class SessionStorageManager {
public static getObject<T>(key: string): T{
return JSON.parse(sessionStorage.getItem(key)) as T;
}
}
const test = SessionStorageManager.getObject('test');

在这个例子中,T没有默认值,而getObject是定义的,我在调用getObject时也没有设置类型,所以根据TypeScript,test的类型是unknown

如果你想确保getObject返回的任何东西都是Object的类型,那么在创建泛型函数时,你就需要使用extends来向TypeScript解释这一点。例如:

public static getObject<T extends object>(key: string): T{
return JSON.parse(sessionStorage.getItem(key)) as T;
}

但是,要注意这里使用as实际上是绕过类型检查。只有当您确信您将始终从JSON.parse中获得对象时才这样做。请记住,在JSON.parse('2')JSON.parse("string")等情况下,您仍然可以从中获得原语,如果JSON字符串格式错误,则JSON.parse将抛出错误。

如果您想要类型安全,那么您将需要添加一个类型检查来支持as类型断言。根据您如何使用这段代码,您可能需要在getObject函数之外执行此操作,因为您现在可能知道可能需要使用什么自定义类型保护来确定对象是否真的属于T的给定类型。

为了确保必须使用这些类型保护,你可能想让getObject返回类型unknown,这意味着TypeScript不会让你对类型做出任何假设,直到你做了一些类型缩小。例如:

class SessionStorageManager {
public static getObject(key: string): unknown {
return JSON.parse(sessionStorage.getItem(key));
}
}
const obj = SessionStorageManager.getObject('test'); // type unknown
if (typeof obj === 'object') {
// type object
}

不幸的是,泛型只在TypeScript中作为某种模型设计辅助而出现。它永远不会被编译成JavaScript文件。以sessionStorage服务为例:

// SessionStorageService.ts
class SessionStorageService {
public static getObject<T>(key: string): T{
return JSON.parse(sessionStorage.getItem(key)) as T;
}
}

如果我们使用tsc将上面的TypeScript文件编译成JavaScript,我们可以发现T被完全删除了:

// SessionStorageService.js
var SessionStorageService = /** @class */ (function () {
function SessionStorageService() {
}
SessionStorageService.getObject = function (key) {
return JSON.parse(sessionStorage.getItem(key));
};
return SessionStorageService;
}());

要在反序列化中返回带有类/原型的对象,需要在序列化过程中保存类/原型信息。否则,这些类/原型信息将丢失。

我做了一个名为esserializer的npm模块来自动解决这个问题:在序列化过程中以纯JSON格式保存JavaScript类实例值,以及类名信息。稍后,在反序列化阶段(可能在另一个进程或另一台机器上),序列化器可以使用相同的类定义递归反序列化对象实例,保留所有类/属性/方法信息。对于SessionStorageService,它看起来像:

// SessionStorageService.ts
const ESSerializer = require('esserializer');
ESSerializer.registerClasses([ClassA, ClassB]); //register all classes possibly involved in serialization.
class SessionStorageService {
public static getObject(key: string){
return ESSerializer.deserialize(sessionStorage.getItem(key));
}
}
// Previously, set sessionStorage in another file
const ESSerializer = require('esserializer');
sessionStorage.setItem(key, ESSerializer.serialize(anObj));

您可以使用Object.create来创建具有正确原型的空对象,使用Object.assign来添加JSON数据中的所有属性。

注意,当你调用这个函数时,你需要传递cls作为一个参数,因为否则就没有办法知道你想要设置哪个类作为原型。如果调用代码不一定知道对象应该是哪个类,你可以想出一些方法来编码这个在你的key(例如,而不是someKey,你可以使它YourClass-someKey,然后有一个类的注册表,这样你就可以通过名称查找YourClass)。

type ClassOf<T> = new(...args: any[]) => T
function makeObject<T>(cls: ClassOf<T>, json: string): T {
const obj: T = Object.create(cls.prototype);
Object.assign(obj, JSON.parse(json));
return obj;
}

使用例子:

class Kitten {
constructor(public name: string) {}
meow(): void { console.log(this.name + ' says meow!'); }
}
// const mittens: Kitten
const mittens = makeObject(Kitten, '{"name": "Mittens"}');
// Mittens says meow!
mittens.meow();

操场上联系

最新更新