Перегрузка конструктора в 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 он выдает определение дублирующего конструктора - что очевидно. Любая помощь приветствуется.

Ответ 1

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

или две перегрузки с более общим конструктором, как в,

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

Ответ 2

Относительно перегрузок конструктора одной хорошей альтернативой будет реализация дополнительных перегрузок как статических фабричных методов. Я думаю, что это более читабельно и просто, чем проверка всех возможных комбинаций аргументов в конструкторе. Вот простой пример:

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. Поскольку вам придется выполнять всю тяжелую работу для обработки различных наборов аргументов, я не вижу большого преимущества в использовании перегрузок вместо отдельных методов.

Ответ 3

Обратите внимание, что вы также можете обойти отсутствие перегрузки на уровне реализации с помощью параметров по умолчанию в 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;
    }   
}

Изменить: С 5 по 16 декабря см. ответ Benson для более сложного решения, которое обеспечивает большую гибкость.

Ответ 4

Примечание: это было упрощено и обновлено 13.04.2017, чтобы отразить TypeScript 2.1, см. Историю для ответа TypeScript 1.8.

Похоже, вы хотите, чтобы параметр объекта был необязательным, а также каждое из свойств объекта было необязательным. В приведенном примере синтаксис перегрузки не требуется. Я хотел бы указать на некоторые плохие практики в некоторых ответах здесь. Конечно, это не наименьшее возможное выражение по существу записи 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;
     }
 }

Это очень безопасный способ записи параметров, которые могут иметь не все свойства определенного объекта. Теперь вы можете безопасно написать любой из них:

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

Приложение: Установка значений по умолчанию: неправильный путь

|| (или) оператор

Учитывайте опасность ||/или операторы при установке резервных значений по умолчанию, как показано в некоторых других ответах. Этот код ниже иллюстрирует неправильный способ установки значений по умолчанию. Вы можете получить неожиданные результаты при сравнении со значениями Falsey, такими как 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 (это, PARAMS)

В моих тестах использование деструктурированного объекта es6/typescript может быть почти на 90% быстрее, чем Object.assign. Использование деструктурированного параметра разрешает только методы и свойства, которые вы присвоили объекту. Например, рассмотрим этот метод:

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

Ответ 5

Я знаю, что это старый вопрос, но новый в 1.4 - типы объединения; используйте их для всех перегрузок функций (включая конструкторы). Пример:

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

Ответ 6

Обновление (8 июня 2017 года): guyarad и snolflake дают действительные баллы в своих комментариях ниже к моему ответу. Я бы рекомендовал читателям посмотреть ответы Benson, Joe и snolflake, у которых лучшие ответы, чем у меня.

Оригинальный ответ (27 января 2014 года)

Другой пример того, как добиться перегрузки конструктора:

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

Ответ 7

Вы можете справиться с этим:

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

Ответ 8

На самом деле это может быть слишком поздно для этого ответа, но теперь вы можете сделать это:

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

поэтому вместо статических методов вы можете сделать выше. Я надеюсь, что это поможет вам!

Ответ 9

В случае, когда необязательный, типизированный параметр достаточно хорош, рассмотрите следующий код, который выполняет то же самое, не повторяя свойств или определяя интерфейс:

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

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

Помните, что это назначит все свойства, переданные в track, но если они не определены на track.

Ответ 10

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

Ответ 11

Мы можем смоделировать перегрузку конструктора, используя охрану

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

Ответ 12

Я использую следующую альтернативу, чтобы получить стандартные/необязательные параметры и конструкторы типа "перегружены" с переменным количеством параметров:

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

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

Я знаю, что это не самый красивый код, но к нему привыкаешь. Нет необходимости в дополнительном интерфейсе, и он допускает закрытые члены, что невозможно при использовании интерфейса.

Ответ 13

Вы должны иметь в виду, что...

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