523

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

我现在正在尝试一个非常基本的类声明;它看起来像这样,

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;
    }
}

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

4

17 回答 17

388

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?.x ?? 0
        this.y = obj?.y ?? 0
        this.height = obj?.height ?? 0
        this.width = obj?.width ?? 0;
    }   
}

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

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?: IBox) {    
        this.x = obj?.x ?? 0
        this.y = obj?.y ?? 0
        this.height = obj?.height ?? 0
        this.width = obj?.width ?? 0;
    }   
}

操场上看

于 2012-10-03T06:14:22.700 回答
197

关于构造函数重载,一个不错的选择是将附加重载实现为静态工厂方法。我认为它比在构造函数中检查所有可能的参数组合更具可读性和更容易。

在下面的示例中,我们能够使用来自保险提供商的数据创建一个患者对象,该数据以不同的方式存储值。为了支持另一种用于患者实例化的数据结构,可以简单地添加另一种静态方法,以便在对提供的数据进行规范化后尽可能地调用默认构造函数。

class Patient {
    static fromInsurance({
        first, middle = '', last,
        birthday, gender
    }: InsuranceCustomer): Patient {
        return new this(
            `${last}, ${first} ${middle}`.trim(),
            utils.age(birthday),
            gender
        );
    }

    constructor(
        public name: string,
        public age: number,
        public gender?: string
    ) {}
}

interface InsuranceCustomer {
    first: string,
    middle?: string,
    last: string,
    birthday: string,
    gender: 'M' | 'F'
}


const utils = { /* included in the playground link below */};

{// Two ways of creating a Patient instance
    const
        jane = new Patient('Doe, Jane', 21),
        alsoJane = Patient.fromInsurance({ 
            first: 'Jane', last: 'Doe',
            birthday: 'Jan 1, 2000', gender: 'F'
        })

    console.clear()
    console.log(jane)
    console.log(alsoJane)
}

您可以在TS Playground查看输出


比如说, TypeScript中的方法重载并不是真正的,因为它需要太多编译器生成的代码,而 TS 旨在不惜一切代价避免这种情况。方法重载的主要用例可能是为 API 中具有魔术参数的库编写声明。由于处理不同的可能参数集的所有繁重工作都是由您完成的,因此我认为在每种情况下使用重载而不是临时方法没有多大优势。

于 2016-07-31T21:08:19.627 回答
107

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

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

     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;
     }
 }

需要添加方法吗? 一个冗长但更可扩展的替代方案:上面 的Box类可以作为接口工作,因为它们是相同的。如果您选择修改上述类,您将需要为传入参数对象定义和引用一个新接口,因为Box该类不再看起来与传入参数完全相同。?:注意在这种情况下,表示可选属性的问号 ( ) 移动的位置。由于我们在类中设置默认值,因此它们保证存在,但它们在传入参数对象中是可选的:

    interface BoxParams {
        x?: number;
         // Add Parameters ...
    }

    class Box {
         public x: number;
         // Copy Parameters ...
         constructor(params: BoxParams = {} as BoxParams) {
         let { x = 0 } = params;
         this.x = x;
    }
    doSomething = () => {
        return this.x + this.x;
        }
    }

无论您选择哪种方式来定义您的类,这种技术都提供了类型安全的护栏,但可以灵活地编写以下任何一种:

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

编译后,您会看到仅在未定义可选值时如何使用默认设置;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;
}());

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

||或)运算符

在设置默认后备值时考虑 /or 运算符的危险,||如其他一些答案所示。下面的代码说明了设置默认值的错误方法。在针对0、''、null、undefined、false、NaN 等虚假值进行评估时,您可能会得到意想不到的结果:

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

Object.assign(this,params)

在我的测试中,使用 es6/typescript 解构对象可以比 Object.assign 快 15-90%。使用解构参数仅允许您分配给对象的方法和属性。例如,考虑这种方法:

class BoxTest {
    public x?: number = 1;

    constructor(params: BoxTest = {} as BoxTest) {
        Object.assign(this, params);
    }
}

如果另一个用户没有使用 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')
于 2016-12-05T14:30:22.310 回答
82

Note that you can also work around the lack of overloading at the implementation level through default parameters in TypeScript, e.g.:

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;
    }   
}

Edit: As of Dec 5 '16, see Benson's answer for a more elaborate solution that allows more flexibility.

于 2012-10-09T06:29:27.643 回答
50

更新 2(2020 年 9 月 28 日):这种语言在不断发展,因此如果您可以使用Partial(在 v2.1 中引入),那么现在这是我实现这一目标的首选方式。

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

   public constructor(b: Partial<Box> = {}) {
      Object.assign(this, b);
   }
}

// Example use
const a = new Box();
const b = new Box({x: 10, height: 99});
const c = new Box({foo: 10});          // Will fail to compile

更新(2017 年 6 月 8 日): guyarad 和 snolflake 在下面对我的回答的评论中提出了有效的观点。我建议读者看看BensonJoesnolflake的答案,他们的答案比我的要好。*

原始答案(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;
    }
  }
}

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

于 2014-01-27T16:02:44.593 回答
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);
于 2015-02-04T18:28:59.987 回答
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;
    }
}

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

于 2019-05-26T20:31:04.153 回答
8

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

class Box {
  x: number;
  y: number;
  height: number;
  width: number;
  constructor(obj?: Partial<Box>) {    
     assign(this, obj);
  }
}

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

例如:你可以new Box({x,y})不用高度和宽度。

于 2018-09-11T09:36:27.423 回答
7

您的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 { ... }
}
于 2020-01-11T04:28:39.300 回答
4
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) {
        const { x, y, height, width } = { x: 0, y: 0, height: 0, width: 0, ...obj }
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
}
于 2016-11-30T05:11:40.190 回答
4

如果可选的类型化参数足够好,请考虑以下代码,它可以在不重复属性或定义接口的情况下完成相同的操作:

export class Track {
   public title: string;
   public artist: string;
   public lyrics: string;

   constructor(track?: Track) {
     Object.assign(this, track);
   }
}

请记住,这将分配所有传入的属性track,即使它们未定义在Track.

于 2016-11-06T00:22:21.230 回答
2

我们可以使用警卫模拟构造函数重载

interface IUser {
  name: string;
  lastName: string;
}

interface IUserRaw {
  UserName: string;
  UserLastName: string;
}

function isUserRaw(user): user is IUserRaw {
  return !!(user.UserName && user.UserLastName);
}

class User {
  name: string;
  lastName: string;

  constructor(data: IUser | IUserRaw) {
    if (isUserRaw(data)) {
      this.name = data.UserName;
      this.lastName = data.UserLastName;
    } else {
      this.name = data.name;
      this.lastName = data.lastName;
    }
  }
}

const user  = new User({ name: "Jhon", lastName: "Doe" })
const user2 = new User({ UserName: "Jhon", UserLastName: "Doe" })
于 2019-03-27T00:18:23.343 回答
1

我使用以下替代方法来获取默认/可选参数和具有可变参数数量的“重载类型”构造函数:

private x?: number;
private y?: number;

constructor({x = 10, y}: {x?: number, y?: number}) {
 this.x = x;
 this.y = y;
}

我知道这不是有史以来最漂亮的代码,但人们已经习惯了。不需要额外的接口,它允许私有成员,这在使用接口时是不可能的。

于 2019-09-20T11:28:12.817 回答
1

正如@Benson 回答中所评论的,我在我的代码中使用了这个示例,我发现它非常有用。但是,当我尝试使用我的类变量类型进行计算时,我发现了Object is possibly 'undefined'.ts(2532)错误,因为问号导致它们是 type AssignedType | undefined。即使在以后的执行中处理未定义的情况或使用编译器类型强制执行,<AssignedType>我也无法摆脱错误,因此无法使 args 成为可选。我解决了为带有问号参数和类变量的参数创建一个单独的类型没有问号。冗长,但有效。

这是原始代码,在类方法()中给出错误,见下文:

/** @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
    const {
      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;
  }

  method(): void {
    const total = this.x + 1; // ERROR. Object is possibly 'undefined'.ts(2532)
  }
}

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

所以变量不能在类方法中使用。如果像这样更正,例如:

method(): void {
    const total = <number> this.x + 1;
}

现在出现这个错误:

Argument of type '{ x: number; y: number; width: number; height: number; }' is not 
assignable to parameter of type 'Box'.
Property 'method' is missing in type '{ x: number; y: number; width: number; height: 
number; }' but required in type 'Box'.ts(2345)

好像整个参数包不再是可选的了。

因此,如果创建了具有可选参数的类型,并且从可选中删除了类变量,我实现了我想要的,参数是可选的,并且能够在类方法中使用它们。在解决方案代码下方:

type BoxParams = {
  x?: number;
  y?: number;
  height?: number;
  width?: number;
}

/** @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: BoxParams = {} as BoxParams) {
    // Define the properties of the incoming `params` object here. 
    // Setting a default value with the `= 0` syntax is optional for each parameter
    const {
      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;
  }

  method(): void {
    const total = this.x + 1;
  }
}

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

任何花时间阅读并尝试理解我要​​表达的观点的人都可以发表评论。

提前致谢。

于 2020-10-20T19:37:00.267 回答
1

这是一个工作示例,您必须考虑每个具有更多字段的构造函数都应将额外字段标记为optional.

class LocalError {
  message?: string;
  status?: string;
  details?: Map<string, string>;

  constructor(message: string);
  constructor(message?: string, status?: string);
  constructor(message?: string, status?: string, details?: Map<string, string>) {
    this.message = message;
    this.status = status;
    this.details = details;
  }
}
于 2020-03-04T08:41:45.517 回答
0

一般来说,对于 N 个重载,最好使用:

constructor(obj?: {fromType1: IType1} | {fromType2: IType2}) {    
    if(obj){
      if(obj.fromType1){
        //must be of form IType1
      } else if(obj.fromType2){
        //must have used a IType2
      } else {
        throw "Invalid argument 1"
      }
    } else {
      //obj not given
    }
}   

至少现在我们可以检查走哪条路线并采取相应的行动

于 2021-07-19T17:19:56.483 回答
-6

你应该记住...

contructor()

constructor(a:any, b:any, c:any)

它与new()或相同new("a","b","c")

因此

constructor(a?:any, b?:any, c?:any)

和上面一样,更灵活...

new()new("a")new("a","b")new("a","b","c")

于 2018-06-02T14:15:35.877 回答