基类属性初始化器

时间:2018-10-10 14:13:45

标签: typescript

我正在寻找一种为基类提供属性初始化程序的方法,该基类将被所有继承类重用。这是为了在继承类中允许必需的属性,而无需在每个类中手动定义构造函数。

class A extends Base {
  /*
    I don't want this line to throw "Property 'a' has
    no initializer and is not definitely assigned in the constructor."
  */
  a: number
}

const a = new A({ a: 123 })

我认为这样会有意义:

class Base<T> {
  constructor(props: T) {
    (Object.keys(props) as Array<keyof T>).forEach(key => {
      (this as T)[key] = props[key]
    })
  }

  base: number
}

class A extends Base<A> {
  a: string
}

但是TS不喜欢(this as T)-(this as any as T)解决了这个问题,这显然是胡说八道。

此外,更重要的是,TS仍然认为a尚未初始化(似乎不了解Base构造函数)。不仅如此-它甚至不认识到Base自身的base属性是在构造函数中初始化的。

这可以在TS中实现吗?能够class B extends A<B>而不用在每个类中重写泛型逻辑的情况更进一步吗?

假设:

  • 完全严格模式
  • !-我希望它尽可能安全地输入

1 个答案:

答案 0 :(得分:0)

首先,我将使用Object.assign更清洁且本机支持的(或将很快使用),如果Bowser缺少对此功能的支持,则添加polyfill。要在Typescript中使用Object.assign,您需要定位es2015,或使用"lib": ["es2015","dom","scripthost"]

最简单的解决方案是在属性的声明中使用!(即确定的赋值声明)。这就是为此创建的,请参见PR

class Base<T> {
  constructor(props: T) {
    Object.assign(this, props)
  }

  base!: number
}

class A extends Base<A> {
  a!: string
}

如果您真的不想使用(!),则可以使用函数来稍微更改继承。初始化的属性将需要在该类的extends子句中指定。这使我们可以绕过确定的分配检查,但这并不是特别漂亮:

class Base<T> {
    constructor(props: T) {
        Object.assign(this, props)
    }
    base!: number // notyhing we can do about this one 
}

function withProps<TProps>(t: new <T>(props: T) => Base<T>): new () => Base<TProps> & TProps {
    return t as any;
}

class A extends withProps<{
    a: string
}>(Base) {
    m() {
        this.a; //ok
    }
}

我们也可以使用这种方法来启用您的最后一种情况,但这涉及更大的类型手术:

class Base<T> {
    constructor(props: T) {
        Object.assign(this, props)
    }
    base!: number // notyhing we can do about this one 
}

type FirstArgument<T extends new <U>(props: any) => any> = T extends new <U>(props: infer A) => any ? A: never
type GenericInstanceType<T extends new <U>(props: any) => any> = T extends new <U>(props: any) => infer R ? R: never
function withProps<TProps>(){
    return function<TClass extends  new <T>(props: any) => Base<T>>(t:TClass): new (props: FirstArgument<TClass> & TProps) => GenericInstanceType<TClass> & TProps {
       return t as any;
    }
}

class A extends withProps<{ a: number }>()(Base) {
    m(){
        this.a
        this.base;
    }
}

class B extends withProps<{ b: string }>()(A){
    m(){
        this.a
        this.b
    }
}