web-dev-qa-db-ja.com

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

Tsc BoxSample.tsで実行すると、重複したコンストラクタ定義がスローされます - これは明らかです。任意の助けは大歓迎です。

306
Ted

TypeScriptではオーバーロードを宣言できますが、実装は1つだけであり、その実装にはすべてのオーバーロードと互換性のある署名が必要です。あなたの例では、これは以下のようにオプションのパラメータで簡単に行うことができます。

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

またはより一般的なコンストラクタを持つ2つのオーバーロード

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

コンストラクタオーバーロードに関して、1つの良い代替案は静的ファクトリメソッドとして追加のオーバーロードを実装することです。コンストラクタですべての可能な引数の組み合わせをチェックするよりも、読みやすくて単純だと思います。これは簡単な例です:

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

TypeScriptでのメソッドのオーバーロードは、実際のには当てはまりません。コンパイラ生成コードが多すぎるため、コアチームがそれを避けようとしているためです。現在、メソッドオーバーロードが言語に存在する主な理由は、そのAPIに魔法の引数を持つライブラリの宣言を書く方法を提供することです。さまざまな引数のセットを処理するには、自分ですべての面倒な作業を行う必要があるので、個別のメソッドではなくオーバーロードを使用することに大きな利点はありません。

80
kbtzr

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

編集:12月5日現在、 Bensonの答え を参照してください。柔軟性.

76
ShinNoNoir

注:TypeScript 2.1を反映するようにこれは簡略化され、2017年4月13日に更新されました。TypeScript1.8 answer.の歴史を参照してください。

オブジェクトパラメータをオプションにし、オブジェクト内の各プロパティもオプションにしたいようです。この例では、提供されているとおり、オーバーロード構文は必要ありません。私はここでいくつかの答えでいくつかの悪い習慣を指摘したかった。確かに、これは本質的にbox = { x: 0, y: 87, width: 4, height: 0 }を書くのに可能な最小の式ではありませんが、これは記述されているようにクラスからあなたが望むかもしれないすべてのコードヒントを提供します。この例では、1つ、いくつか、all、、または noneのパラメータを使用して関数を呼び出すことができ、それでもデフォルト値を取得できます。

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

これは、オブジェクトのすべてのプロパティが定義されていない可能性のあるパラメータを記述するための非常に安全な方法です。これで、これらを安全に書くことができます。

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

コンパイルすると、オプションのパラメータは本当にオプションであることがわかります。これは、undefinedの省略形であるvar = isOptional || default;をチェックすることで、広く使用されている(ただしエラーが発生しやすい)フォールバック構文のvoid 0を回避します。

コンパイルされた出力

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

補遺:デフォルト値の設定:間違った方法

||(または)演算子

他の回答に示すように、デフォルトのフォールバック値を設定するときは、||または演算子の危険性を考慮してください。以下のこのコードは、デフォルトを設定する間違った方法を示しています。 0、 ''、null、未定義、false、NaNなどの falsey の値に対して評価すると、予期しない結果が生じる可能性があります。

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 よりもほぼ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')
71
Benson

これは古い質問ですが、1.4の新機能は共用体型です。すべての関数オーバーロード(コンストラクタを含む)にこれらを使用してください。例:

class foo {
    private _name: any;
    constructor(name: string | number) {
        this._name = name;
    }
}
var f1 = new foo("bar");
var f2 = new foo(1);
33
Joe

更新(2017年6月8日):guyaradとsnolflakeは私の答えに対して以下のコメントで有効なポイントを作っています。私は読者が BensonJoe そして snolflake 自分よりも良い答えを持っている人

オリジナルの回答(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

21
vegemite4me

これを処理することができます:

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

Partialはあなたのフィールド(x、y、height、width)をオプションにし、複数のコンストラクタを許可します

例:new Box({x,y})は高さと幅なしでできます。

= {}は、undefined、nullなどの偽の値を処理します。そして、new Box()を実行できます。

4
Yacine

オプションの型付けされたパラメータで十分な場合は、プロパティを繰り返したりインタフェースを定義したりせずに同じことを実行する次のコードを検討してください。

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

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

trackで定義されていない場合でも、これはTrackで渡されたすべてのプロパティを割り当てます。

3
parliament

別のバージョンでは、@ ShinNoNoirのコードが好きです。デフォルト値とスプレッド構文を使用します。

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

    constructor({x, y, height, width}: IBox = { x: 0, y: 0, height: 0, width: 0 }) {
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
}
1

実際には、この回答には遅すぎるかもしれませんが、これを実行できます。

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

だから静的メソッドの代わりにあなたは上記を行うことができます。お役に立てば幸いです。

1
Kostas Drak

ガード を使ってコンストラクタオーバーロードをシミュレートすることができます

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" })
0