TypeScript中的构造函数重载

Ted*_*Ted 344 constructor overloading typescript

有没有人在TypeScript中完成构造函数重载.在语言规范的第64页(v 0.8)中,有一些语句描述构造函数重载,但没有给出任何示例代码.

我现在正在尝试一个非常基本的课堂宣言; 看起来像这样,

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj: IBox) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   

    constructor() {
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

当使用tsc BoxSample.ts运行时,它会抛出一个重复的构造函数定义 - 这很明显.任何帮助表示赞赏.

chu*_*ckj 273

TypeScript允许您声明重载,但是您只能有一个实现,并且该实现必须具有与所有重载兼容的签名.在您的示例中,可以使用可选参数轻松完成此操作,如

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj?: IBox) {    
        this.x = obj && obj.x || 0
        this.y = obj && obj.y || 0
        this.height = obj && obj.height || 0
        this.width = obj && obj.width || 0;
    }   
}
Run Code Online (Sandbox Code Playgroud)

或两个带有更通用构造函数的重载,如

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox); 
    constructor(obj?: any) {    
        this.x = obj && obj.x || 0
        this.y = obj && obj.y || 0
        this.height = obj && obj.height || 0
        this.width = obj && obj.width || 0;
    }   
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,应该可以让编译器生成javascript来确定在运行时采取了哪个重载.但这不太可能,因为他们的哲学似乎是尽可能少生成javascript. (8认同)
  • 另一个非常重要的注意事项:虽然TypeScript已经不是类型安全的,但这进一步侵入了它.像这里完成的函数重载丢失了可以检查函数的任何属性.编译器将不再关心并将假定返回的类型是正确的. (3认同)
  • 是什么让这不安全?我们仍然确保类型是```````````public x:number```.安全性在于我们确保参数(如果通过)的类型正确. (2认同)

kbt*_*tzr 104

关于构造函数重载,一种替代方法是将额外的重载实现为静态工厂方法.我认为它比测试你的调用参数更具可读性和混乱性.这是一个简单的例子:

class Person {
    static fromData(data: PersonData) {
        let { first, last, birthday, gender = 'M' } = data 
        return new this(
            `${last}, ${first}`,
            calculateAge(birthday),
            gender
        )
    }

    constructor(
        public fullName: string,
        public age: number,
        public gender: 'M' | 'F'
    ) {}
}

interface PersonData {
    first: string
    last: string
    birthday: string
    gender?: 'M' | 'F'
}


let personA = new Person('Doe, John', 31, 'M')
let personB = Person.fromData({
    first: 'John',
    last: 'Doe',
    birthday: '10-09-1986'
})
Run Code Online (Sandbox Code Playgroud)

比方说,TypeScript中的方法重载不是真的,因为它需要太多的编译器生成的代码,而核心团队试图不惜一切代价避免这种情况.目前,在语言中出现方法重载的主要原因是提供一种在API中使用魔术参数编写库的声明的方法.既然你需要自己完成所有繁重的操作以处理不同的参数集,我认为使用重载而不是分离方法并没有太大的优势.

  • 如果您不总是希望在“data”中出现“first”、“last”和“birthday”,则可以使用“(data: Partial<PersonData>)”。 (9认同)
  • 另外,构造函数的访问修饰符可以从“public”更改为“private”/“protected”,然后创建对象的唯一方法就是静态工厂方法。有时这非常有用。 (2认同)

Ben*_*son 81

注意:这是在4/13/2017简化和更新以反映TypeScript 2.1,请参阅TypeScript 1.8答案的历史记录.

听起来您希望object参数是可选的,并且对象中的每个属性都是可选的.在示例中,如所提供的,不需要重载语法.我想在这里的一些答案中指出一些不好的做法.当然,这不是本质上写作的最小可能表达box = { x: 0, y: 87, width: 4, height: 0 },但是这提供了所有可能想要从类中描述的细节的代码.此示例允许您使用一个,一些,全部没有参数调用函数,并仍然获取默认值.

 /** @class */
 class Box {
     public x?: number;
     public y?: number;
     public height?: number;
     public width?: number;     

     // The Box class can work double-duty as the interface here since they are identical
     // If you choose to add methods or modify this class, you will need to
     // define and reference a new interface for the incoming parameters object 
     // e.g.:  `constructor(params: BoxObjI = {} as BoxObjI)` 
     constructor(params: Box = {} as Box) {

         // Define the properties of the incoming `params` object here. 
         // Setting a default value with the `= 0` syntax is optional for each parameter
         let {
             x = 0,
             y = 0,
             height = 1,
             width = 1
         } = params;

         //  If needed, make the parameters publicly accessible
         //  on the class ex.: 'this.var = var'.
         /**  Use jsdoc comments here for inline ide auto-documentation */
         this.x = x;
         this.y = y;
         this.height = height;
         this.width = width;
     }
 }
Run Code Online (Sandbox Code Playgroud)

这是一种非常安全的方法来编写可能没有定义对象的所有属性的参数.您现在可以安全地编写以下任何内容:

const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({x:0});
const box4 = new Box({x:0, height:10});
const box5 = new Box({x:0, y:87,width:4,height:0});

 // Correctly reports error in TypeScript, and in js, box6.z is undefined
const box6 = new Box({z:0});  
Run Code Online (Sandbox Code Playgroud)

编译后,你会看到可选参数确实是可选的,它避免了var = isOptional || default;通过检查广泛使用(但容易出错)的回退语法的缺陷void 0,这是以下的简写undefined:

编译输出

var Box = (function () {
    function Box(params) {
        if (params === void 0) { params = {}; }
        var _a = params.x, x = _a === void 0 ? 0 : _a, _b = params.y, y = _b === void 0 ? 0 : _b, _c = params.height, height = _c === void 0 ? 1 : _c, _d = params.width, width = _d === void 0 ? 1 : _d;
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
    return Box;
}());
Run Code Online (Sandbox Code Playgroud)

附录:设置默认值:错误的方式

||(或)运算符

||在设置默认回退值时,请考虑/或运算符的危险,如其他一些答案所示.下面的代码说明了设置默认值的错误方法.针对评估时,可以得到意想不到的结果falsey如0值,"",null,未定义,假的,楠:

var myDesiredValue = 0;
var result = myDesiredValue || 2;

// This test will correctly report a problem with this setup.
console.assert(myDesiredValue === result && result === 0, 'Result should equal myDesiredValue. ' + myDesiredValue + ' does not equal ' + result);
Run Code Online (Sandbox Code Playgroud)

Object.assign(此,则params)

在我的测试中,使用es6/typescript destructured对象几乎比Object.assign快90%.使用destructured参数只允许您为对象分配的方法和属性.例如,考虑这种方法:

class BoxTest {
    public x?: number = 1;

    constructor(params: BoxTest = {} as BoxTest) {
        Object.assign(this, params);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果另一个用户没有使用TypeScript并试图放置一个不属于的参数,比如说,他们可能会尝试放置一个z属性

var box = new BoxTest({x: 0, y: 87, width: 4, height: 0, z: 7});

// This test will correctly report an error with this setup. `z` was defined even though `z` is not an allowed property of params.
console.assert(typeof box.z === 'undefined')
Run Code Online (Sandbox Code Playgroud)

  • 我已经更新了我的答案以删除多余的转换,这是从 Typescript 1.8 编码中遗留下来的。剩下的转换是针对空对象的(如果没有定义参数,{} 将成为默认对象;并且由于 `{}` 不作为 Box 进行验证,我们将其转换为 Box。以这种方式进行转换允许我们创建一个没有定义任何参数的新 Box。在您的 IDE 中,您可以输入我的示例以及 `const box1 = new Box();` 行,您可以看到强制转换如何解决我们的一些错误消息见使用场景。 (2认同)

Shi*_*oir 76

请注意,您还可以通过TypeScript中的默认参数解决实现级别缺少重载的问题,例如:

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj : IBox = {x:0,y:0, height:0, width:0}) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   
}
Run Code Online (Sandbox Code Playgroud)

编辑: 自16年12月5日起,请参阅Benson的答案,了解更精细的解决方案,从而提供更大的灵活性.


Joe*_*Joe 33

我知道这是一个老问题,但1.4中的新内容是联合类型; 将这些用于所有函数重载(包括构造函数).例:

class foo {
    private _name: any;
    constructor(name: string | number) {
        this._name = name;
    }
}
var f1 = new foo("bar");
var f2 = new foo(1);
Run Code Online (Sandbox Code Playgroud)


veg*_*4me 22

更新(2017年6月8日): guyarad和snolflake在下面的评论中给出了有效的答案.我建议读者看看Benson,Joesnolflake的答案,他们的答案比我的好.

原答案(2014年1月27日)

另一个如何实现构造函数重载的例子:

class DateHour {

  private date: Date;
  private relativeHour: number;

  constructor(year: number, month: number, day: number, relativeHour: number);
  constructor(date: Date, relativeHour: number);
  constructor(dateOrYear: any, monthOrRelativeHour: number, day?: number, relativeHour?: number) {
    if (typeof dateOrYear === "number") {
      this.date = new Date(dateOrYear, monthOrRelativeHour, day);
      this.relativeHour = relativeHour;
    } else {
      var date = <Date> dateOrYear;
      this.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
      this.relativeHour = monthOrRelativeHour;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

资料来源:http://mimosite.com/blog/post/2013/04/08/Overloading-in-TypeScript

  • 这不是一个建设性的评论 - 但是,哇,这很难看.在TypeScript中错过了*type*safety的观点...... (20认同)
  • 那是构造函数重载吗?!不,谢谢!我宁愿为那个类实现一个静态工厂方法,确实很难看。 (2认同)
  • 我想今天我们可以有 dateOrYear: Date | 数字, (2认同)

Kos*_*rak 15

实际上,这个答案可能为时已晚,但您现在可以这样做:

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox);
    constructor(obj?: IBox) {    
        this.x = !obj ? 0 : obj.x;
        this.y = !obj ? 0 : obj.y;
        this.height = !obj ? 0 : obj.height;
        this.width = !obj ? 0 : obj.width;
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,您可以执行上述操作,而不是静态方法。希望对你有帮助!!!


Mud*_*abs 6

您的Box类正在尝试定义多个构造函数实现

仅最后一个构造函数重载签名用作类构造函数实现

在下面的示例中,请注意构造函数实现的定义使其与前面的任何一个重载签名都不矛盾。

interface IBox = {
    x: number;
    y: number;
    width: number;
    height: number;
}

class Box {
    public x: number;
    public y: number;
    public width: number;
    public height: number;

    constructor() /* Overload Signature */
    constructor(obj: IBox) /* Overload Signature */
    constructor(obj?: IBox) /* Implementation Constructor */ {
        if (obj) {
            this.x = obj.x;
            this.y = obj.y;
            this.width = obj.width;
            this.height = obj.height;
        } else {
            this.x = 0;
            this.y = 0;
            this.width = 0;
            this.height = 0
        }
    }

    get frame(): string {
        console.log(this.x, this.y, this.width, this.height);
    }
}

new Box().frame; // 0 0 0 0
new Box({ x:10, y:10, width: 70, height: 120 }).frame; // 10 10 70 120



// You could also write the Box class like so;
class Box {
    public x: number = 0;
    public y: number = 0;
    public width: number = 0;
    public height: number = 0;

    constructor() /* Overload Signature */
    constructor(obj: IBox) /* Overload Signature */
    constructor(obj?: IBox) /* Implementation Constructor */ {
        if (obj) {
            this.x = obj.x;
            this.y = obj.y;
            this.width = obj.width;
            this.height = obj.height;
        }
    }

    get frame(): string { ... }
}
Run Code Online (Sandbox Code Playgroud)


Yac*_*ine 5

您可以通过以下方式处理:

import { assign } from 'lodash'; // if you don't have lodash use Object.assign
class Box {
    x: number;
    y: number;
    height: number;
    width: number;
    constructor(obj: Partial<Box> = {}) {    
         assign(this, obj);
    }
}
Run Code Online (Sandbox Code Playgroud)

局部将使您的字段(x,y,高度,宽度)为可选,从而允许多个构造函数

例如:您可以new Box({x,y})没有高度和宽度。

= {}会处理,如不定falsy值,空等等,然后你可以做new Box()

  • 我认为您仍然需要处理缺失项目的默认值。很容易完成,呵呵。 (2认同)