如何从TypeScript中的泛型参数扩展?

时间:2017-01-25 01:17:12

标签: generics inheritance typescript

我正在研究一种模式,其中抽象父类需要继承通过其子类提供的泛型类型的所有属性。

这里看一下基类的构造函数,它提供了一个基本的想法,也可以是viewed on github

public constructor(data: T) {

    _.assign(this, _.omit(data, [
        "table",
        "id",
        "PartitionKey",
        "RowKey",
    ]));
}

我正在寻找的是一种有效表达this的方式:

export abstract class Base<T extends EntityData> implements Storage.AbstractEntity & T {
...

不幸的是,目前似乎并没有支持这一点,所以我不知道如何让我的基类扩展其泛型参数,以便任何继承它的类{{3 }}:

export class AuthorFact extends Base<Author> {

    public get RowKey() { return (this as any).id }
}

正如您所看到的,我被迫从我的基本类型中删除& T并使用(this as any)来禁止编译器抛出错误。

我最终希望.id上的类型检查成功,以及{I}创建的实例上的Author的任何其他属性。

1 个答案:

答案 0 :(得分:2)

即使使用allows classes and interfaces to derive from object types and intersections of object types的最新提交,仍然不允许从泛型类型参数扩展类:

  

接口或类不能扩展裸类型参数,因为它   无法一致地验证没有成员名称   类型实例化中的冲突。

但是现在允许的是疯狂的:你可以在运行时构建类并对它们进行类型检查。 (这需要npm i typescript@next,目前是2.2):

import * as Azure from "azure";
import * as _ from "lodash";


export class Author {
    public id: string;
    public firstName: string;
    public lastName: string;
    public nativeIds: {[key: string]: string} = {};
    public posts: Post[];
    public created: Date;
}

export class Post {
   public constructor(
        public id: string,
        public author: Author,
        public title: string,
        public content: string,
        public authored: Date
    ) {
    }
}

type Constructor<T> = new () => T;
type DataConstructor<T, Data> = new (data: Data) => T;

function Base<T>(dataClass: Constructor<T>) {
    class Class extends (dataClass as Constructor<{}>) {
        public constructor(data: T) {
            super();
            _.assign(this, _.omit(data, [
                "table",
                "id",
                "PartitionKey",
                "RowKey",
            ]));
        }
        [property: string]: string | number | boolean | Date;
    }
    return Class as Constructor<T & Class>;
}

function Fact<T, Data>(superClass: DataConstructor<T, Data>) {
    class Class extends (superClass as DataConstructor<{}, Data>) {
        public get PartitionKey() { return "fact" }
    }
    return Class as DataConstructor<T & Class, Data>
}

function Identifiable<T, Data>(superClass: DataConstructor<T, Data>) {
    class Class extends (superClass as DataConstructor<{}, Data>) {
        public id: string;
        public get RowKey() { return this.id }
    }
    return Class as DataConstructor<T & Class, Data>
}

function IdentifiableDataFact<Data>(dataClass: Constructor<Data>) {
    return Identifiable(Fact(Base(dataClass)));
}


class AuthorFact extends IdentifiableDataFact(Author) {
}

// let's init some data
let author = new Author();
author.id = 'a';
author.firstName = 'z';
author.lastName = 'q';


// let's see what we've got    
let authorFact = new AuthorFact(author); // it has a constructor that takes Author

let e: Azure.Entity = authorFact; // it's structurally compatible with Azure.Entity

// it has PartitionKey
console.log(authorFact.PartitionKey);         // prints fact

// it has some properties that were copied over by Base constructor (except id)
console.log(authorFact.firstName);     // prints z

// it has index signature (check with --noImplicitAny)
const ps = ['lastName', 'nativeIds'];
ps.forEach(p => console.log(authorFact[p]));  // prints q {}

// and it has RowKey but it's undefined here (this might not be what you want)
// because id is explicitly omitted from being copied in Base constructor
console.log(authorFact.RowKey);    // undefined 

事实证明,你不能用抽象类做到这一点,但我认为结构类型仍然允许你在这里做你想做的事。