web-dev-qa-db-fra.com

Surcharge du constructeur dans TypeScript

Quelqu'un at-il surchargé le constructeur dans TypeScript? À la page 64 de la spécification de langue (v 0.8), il y a des instructions décrivant les surcharges du constructeur, mais aucun exemple de code n'a été fourni.

J'essaie actuellement une déclaration de classe très basique; ça ressemble à ça,

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

Lorsqu'il est exécuté avec tsc BoxSample.ts, il supprime une définition de constructeur en double - ce qui est évident. Toute aide est appréciée.

322
Ted

TypeScript vous permet de déclarer des surcharges, mais vous ne pouvez avoir qu'une seule implémentation et cette implémentation doit avoir une signature compatible avec toutes les surcharges. Dans votre exemple, cela peut facilement être fait avec un paramètre optionnel comme dans,

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

ou deux surcharges avec un constructeur plus général comme dans,

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

Concernant surcharges de constructeur, une bonne alternative serait d'implémenter les surcharges supplémentaires en tant que méthodes d'usine statique. Je pense que c'est plus lisible et simple que de vérifier toutes les combinaisons d'arguments possibles chez le constructeur. Voici un exemple simple:

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

La surcharge de méthode dans TypeScript n’est pas réelle , disons, car elle nécessiterait trop de code généré par le compilateur et l’équipe principale essaie de l’éviter à tous les coûts. Actuellement, la surcharge de méthodes est présente sur le langage principalement pour fournir un moyen d'écrire des déclarations pour les bibliothèques avec des arguments magiques dans leur API. Etant donné que vous devez faire le gros du travail vous-même pour gérer différents jeux d'arguments, je ne vois pas d'avantage à utiliser des surcharges au lieu de méthodes séparées.

93
kbtzr

Notez que vous pouvez également contourner le manque de surcharge au niveau de la mise en œuvre via les paramètres par défaut dans TypeScript, par exemple:

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: À partir du 5 décembre 2016, voir réponse de Benson pour une solution plus élaborée offrant davantage de flexibilité.

76
ShinNoNoir

Remarque: cette opération a été simplifiée et mise à jour le 13/04/2017 pour refléter TypeScript 2.1, voir l'historique des réponses TypeScript 1.8.

Il semble que vous souhaitiez que le paramètre d'objet soit facultatif et que chacune des propriétés de l'objet soit facultative. Dans l'exemple, tel que fourni, la syntaxe de surcharge n'est pas nécessaire. Je voulais signaler quelques mauvaises pratiques dans certaines des réponses ici. Certes, ce n’est pas la plus petite expression possible d’écrire essentiellement box = { x: 0, y: 87, width: 4, height: 0 }, mais cela fournit toutes les astuces de code que vous pourriez souhaiter obtenir de la classe telle que décrite. Cet exemple vous permet d’appeler une fonction avec un, certains, tous, ou aucun des paramètres et d’obtenir les valeurs par défaut.

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

C'est un moyen très sûr d'écrire pour les paramètres pour lesquels toutes les propriétés de l'objet ne sont pas définies. Vous pouvez maintenant écrire en toute sécurité:

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

Compilé, vous voyez que les paramètres optionnels sont vraiment optionnels, ce qui évite les pièges d'une syntaxe de repli largement utilisée (mais sujette aux erreurs) de var = isOptional || default; en vérifiant par rapport à void 0, qui est un raccourci pour undefined:

La sortie compilée

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

Addendum: Définition des valeurs par défaut: le mauvais sens

L'opérateur || (ou)

Prenez en compte le danger de ||/ou des opérateurs lors de la définition des valeurs de repli par défaut, comme indiqué dans d'autres réponses. Ce code ci-dessous illustre la mauvaise façon de définir les valeurs par défaut. Vous pouvez obtenir des résultats inattendus lorsque vous évaluez des valeurs falsey telles que 0, '', null, non défini, 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)

Dans mes tests, utiliser l'objet déstructuré es6/TypeScript peut être presque 90% plus rapide que Object.assign . L'utilisation d'un paramètre déstructuré autorise uniquement les méthodes et les propriétés que vous avez affectées à l'objet. Par exemple, considérons cette méthode:

class BoxTest {
    public x?: number = 1;

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

Si un autre utilisateur n'utilisait pas TypeScript et tentait de placer un paramètre qui n'appartenait pas, par exemple, il pourrait essayer de mettre une propriété 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')
73
Benson

Je sais que c'est une vieille question, mais la nouveauté de la version 1.4 concerne les types d'union; utilisez-les pour toutes les surcharges de fonctions (y compris les constructeurs). Exemple:

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

Mise à jour (8 juin 2017): guyarad et snolflake marquent des points valides dans leurs commentaires ci-dessous à ma réponse. Je recommanderais aux lecteurs de regarder les réponses par Benson , Joe et snolflake qui ont de meilleures réponses que les miennes.

Réponse originale (27 janvier 2014)

Un autre exemple de la façon d’atteindre la surcharge du constructeur:

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

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

22
vegemite4me

Vous pouvez gérer cela en:

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 rendra vos champs optionnels (x, y, hauteur, largeur), permettant à plusieurs constructeurs

par exemple: vous pouvez faire new Box({x,y}) sans hauteur ni largeur.

Le = {} gérera les valeurs de fausseté telles que indéfinie, null, etc., puis vous pourrez faire new Box()

5
Yacine

En fait, il pourrait être trop tard pour cette réponse, mais vous pouvez maintenant le faire:

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

donc au lieu de méthodes statiques, vous pouvez faire ce qui précède. J'espère que ça vous aidera !!!

4
Kostas Drak

Dans le cas où un paramètre optionnel, typé est suffisant, considérons le code suivant qui accomplit la même chose sans répéter les propriétés ni définir une interface:

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

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

N'oubliez pas que toutes les propriétés passées dans track seront affectées, même si elles ne sont pas définies sur Track.

3
parliament

Une autre version, comme celle de @ ShinNoNoir, utilise les valeurs par défaut et la syntaxe de propagation:

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
Ardeshir Valipoor

Nous pouvons simuler la surcharge du constructeur en utilisant gardes

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
miguel savignano