简体   繁体   English

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

[英]How to assign prototype to generic type

I'm trying to build a sessionStorage service that can parse objects strings to a generic type.我正在尝试构建一个 sessionStorage 服务,该服务可以将对象字符串解析为通用类型。 but with this implementation it only returns an object without prototype (no object functions).但是在这个实现中,它只返回一个没有原型的对象(没有对象函数)。

here is the code:这是代码:

public static getObject<T>(key: string): T{

    return JSON.parse(sessionStorage.getItem(key)) as T;
}

Regards!问候!

JSON does not encode functions, or prototypes, or constructors. JSON 不编码函数、原型或构造函数。 Parsed JSON will never create an instance of any class for you.解析后的 JSON 永远不会为您创建任何类的实例。 It's just raw structured data.它只是原始的结构化数据。 And this is true for most ways that you might serialize javascript data.这对于您可能序列化 javascript 数据的大多数方式都是正确的。

There is no good way around that.没有什么好的方法可以解决这个问题。 It's up to you to structure your codebase so that things will work with this in mind.由您来构建您的代码库,以便在考虑到这一点的情况下工作。

There are an innumerable number of solutions to this, but for example you could pass this data to a constructor after you fetch it.对此有无数的解决方案,但例如,您可以在获取此数据后将其传递给构造函数。

Something like this:像这样的东西:

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

I assume you're using your code somewhat like this?我假设您使用的代码有点像这样?

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

const test = SessionStorageManager.getObject('test');

In this example, T has no default value set where getObject is defined, and I haven't set a type when calling getObject either, so the type of test according to TypeScript is unknown .在这个例子中, T在定义getObject地方没有设置默认值,而且我在调用getObject时也没有设置类型,所以根据 TypeScript 的test类型是unknown

If you want to make sure that anything returned by getObject is a type of Object , then you'll want to use extends when creating your generic function to explain this to TypeScript.如果您想确保getObject返回的任何内容都是Object类型,那么您将需要在创建泛型函数时使用extends来向 TypeScript 解释这一点。 For example:例如:

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

However, be wary that the use of as here is essentially bypassing the type check.但是,请注意这里的as的使用本质上是绕过类型检查。 Only do this if you are confident that you will always be getting an object out of JSON.parse .仅当您确信您将始终从JSON.parse获取对象时才执行此操作。 Remember that you can still get primitives out of it, in cases such as JSON.parse('2') or JSON.parse("string") , and if the JSON string is malformed then JSON.parse will throw an error.请记住,在JSON.parse('2')JSON.parse("string")等情况下,您仍然可以从中获取原语,如果 JSON 字符串格式错误,则JSON.parse将抛出错误。

If you want to be type safe, then you will need to add a type check to support that as type assertion .如果你想要类型安全,那么你需要添加一个类型检查来支持它as 类型断言 Depending on how you are using this code, you might need to do that outside the getObject function since you might now know what custom type guard may need to be used to determine whether or not an object really is of the given type of T .根据您使用此代码的方式,您可能需要在getObject函数之外执行此操作,因为您现在可能知道可能需要使用什么自定义类型保护来确定对象是否确实属于给定类型的T

To ensure these type guards must be used, you may want to instead have getObject return the type unknown , which means TypeScript won't let you make any assumptions about the type until you've done some type narrowing.为了确保必须使用这些类型保护,您可能希望让getObject返回类型unknown ,这意味着 TypeScript 不会让您对类型做出任何假设,直到您完成某种类型缩小。 For example:例如:

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
}

Unfortunately, generic type only happens in TypeScript as some kind of model design assistance.不幸的是,泛型类型仅作为某种模型设计辅助出现在 TypeScript 中。 It will never be compiled into JavaScript file.它永远不会被编译成 JavaScript 文件。 Take your sessionStorage service as an example:以你的 sessionStorage 服务为例:

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

If we compile above TypeScript file into JavaScript using tsc , we can find T is completely removed:如果我们使用tsc上面的 TypeScript 文件编译成 JavaScript,我们可以发现T被完全删除了:

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

To return object with class/prototype in deserialization, you need to save class/prototype information during serialization.反序列化返回带有类/原型的对象,需要在序列化时保存类/原型信息。 Otherwise, these class/prototype information will be lost.否则,这些类/原型信息将丢失。

I've made an npm module named esserializer to solve this problem automatically: save JavaScript class instance values during serialization, in plain JSON format, together with its class name information.我创建了一个名为esserializer的 npm 模块来自动解决这个问题:在序列化期间以纯 JSON 格式保存 JavaScript 类实例值及其类名信息。 Later on, during the deserialization stage (possibly in another process or on another machine), esserializer can recursively deserialize object instance, with all Class/Property/Method information retained, using the same class definition.稍后,在反序列化阶段(可能在另一个进程或另一台机器上),esserializer 可以递归地反序列化对象实例,保留所有类/属性/方法信息,使用相同的类定义。 For your SessionStorageService, it would look like:对于您的 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));

You could do it using Object.create to create an empty object with the correct prototype, and Object.assign to add all of the properties from the JSON data.您可以使用Object.create创建一个具有正确原型的空对象,并使用Object.assign添加来自 JSON 数据的所有属性。

Note that you need to pass cls as an argument when you call this function, since otherwise there is no way of knowing which class you want to set as the prototype.请注意,调用此函数时需要将cls作为参数传递,否则无法知道要将哪个类设置为原型。 If the calling code doesn't necessarily know which class the object should be, you could come up with some way to encode this in your key (eg instead of someKey you could make it YourClass-someKey and then have a registry of classes so you could look up YourClass by name).如果调用代码并不一定知道对象应该是哪一类,你能想出一些方法来在你的编码这个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;
}

Example usage:用法示例:

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();

Playground Link 游乐场链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM