Перегрузка конструктора в TypeScript

284

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

  • 2
    насколько я могу судить, он еще не поддерживает несколько конструкторов
Теги:
constructor
method-overloading

10 ответов

230

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;
    }   
}
  • 5
    На самом деле, должна быть возможность позволить компилятору генерировать javascript для определения во время выполнения, какая перегрузка была принята. Но это маловероятно, потому что их философия заключается в том, чтобы генерировать как можно меньше javascript.
  • 0
    @remcoder, это всегда будет правдой. Некоторые виды информации о типах недоступны во время выполнения. Например, в сгенерированном JavaScript отсутствует концепция интерфейса IBox . Это может работать для классов и встроенных типов, но я полагаю, учитывая потенциальную путаницу вокруг этого, оно было опущено.
Показать ещё 5 комментариев
74

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

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

  • 1
    вы можете использовать (data: Partial<PersonData>) если вы не всегда хотите, чтобы в data присутствовали first , last и birthday .
72

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

56

Примечание: это было упрощено и обновлено 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;
}());

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

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

Учитывайте опасность ||/или операторы при установке резервных значений по умолчанию, как показано в некоторых других ответах. Этот код ниже иллюстрирует неправильный способ установки значений по умолчанию. Вы можете получить неожиданные результаты при сравнении со значениями Фолси, такими как 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')
  • 0
    Я знаю, что это какая-то старая тема, но кастинг Ibox сломал мое мнение, можете ли вы объяснить мне, как это работает?
  • 0
    Я обновил свой ответ, чтобы убрать лишнее приведение, которое было переносом из кодирования для Typescript 1.8. Остается приведение к пустому объекту ({} становится объектом по умолчанию, если параметры не определены; а поскольку {} не проверяется как Box, мы приводим его как Box. Приведение таким образом позволяет нам создать new Box, ни один из его параметров не определен. В вашей IDE вы можете ввести мой пример, а также const box1 = new Box(); строки, и вы можете увидеть, как приведение кода решает некоторые сообщения об ошибках, которые мы видим при использовании. сценарии.
Показать ещё 9 комментариев
32

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

class foo {
    private _name: any;
    constructor(name: string | number) {
        this._name = name;
    }
}
var f1 = new foo("bar");
var f2 = new foo(1);
  • 0
    Не будет ли поле name также иметь тип string | number вместо any ?
  • 0
    Вы можете определенно сделать это, да, и это может быть немного более последовательным, но в этом примере это даст вам доступ только к .toString() и .valueOf() , в Intellisense, поэтому для меня использование any просто отлично, но каждому свое.
21

Обновление (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

  • 18
    Это не конструктивный комментарий - но это ужасно. Вид пропускает точку безопасности типов в TypeScript ...
  • 1
    Это перегрузка конструктора ?! Нет, спасибо! Я бы предпочел реализовать статический метод фабрики для этого класса, действительно ужасно.
Показать ещё 1 комментарий
3

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

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

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

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

1

Другая версия, например, для кода @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;
    }
}
  • 0
    В таком случае, не лучше ли ввести параметр {} вместо IBox ? Вы уже перечисляете ограничения свойства ...
0

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

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

-5

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню