TypeScript и инициализаторы полей

Как создать новый класс в TS таким образом (пример в C#, чтобы показать, что я хочу):

// ... some code before
return new MyClass { Field1 = "ASD", Field2 = "QWE" };
// ...  some code after

РЕШЕНИЕ:
Классический JavaScript синтаксис:

return { Field1: "ASD", Field2: "QWE" };

Ответ 1

Обновление

После написания этого ответа появились лучшие способы. Пожалуйста, посмотрите другие ответы ниже, которые имеют больше голосов и лучший ответ. Я не могу удалить этот ответ, поскольку он помечен как принятый.


Старый ответ

Существует проблема с кодексом TypeScript, которая описывает это: Поддержка инициализаторов объектов.

Как уже говорилось, вы уже можете сделать это, используя интерфейсы в TypeScript вместо классов:

interface Name {
    first: string;
    last: string;
}
class Person {
    name: Name;
    age: number;
}

var bob: Person = {
    name: {
        first: "Bob",
        last: "Smith",
    },
    age: 35,
};

Ответ 2

Обновлено 07/12/2016: Typescript 2.1 вводит сопоставленные типы и предоставляет Partial<T>, что позволяет вам делать это....

class Person {
    public name: string = "default"
    public address: string = "default"
    public age: number = 0;

    public constructor(init?:Partial<Person>) {
        Object.assign(this, init);
    }
}

let persons = [
    new Person(),
    new Person({}),
    new Person({name:"John"}),
    new Person({address:"Earth"}),    
    new Person({age:20, address:"Earth", name:"John"}),
];

Исходный ответ:

Мой подход заключается в определении отдельной переменной fields, которую вы передаете конструктору. Фокус в том, чтобы переопределить все поля класса для этого инициализатора как необязательные. Когда объект создается (с его значениями по умолчанию), вы просто назначаете объект инициализации на this;

export class Person {
    public name: string = "default"
    public address: string = "default"
    public age: number = 0;

    public constructor(
        fields?: {
            name?: string,
            address?: string,
            age?: number
        }) {
        if (fields) Object.assign(this, fields);
    }
}

или сделайте это вручную (более безопасно):

if (fields) {
    this.name = fields.name || this.name;       
    this.address = fields.address || this.address;        
    this.age = fields.age || this.age;        
}

использование:

let persons = [
    new Person(),
    new Person({name:"Joe"}),
    new Person({
        name:"Joe",
        address:"planet Earth"
    }),
    new Person({
        age:5,               
        address:"planet Earth",
        name:"Joe"
    }),
    new Person(new Person({name:"Joe"})) //shallow clone
]; 

и вывод консоли:

Person { name: 'default', address: 'default', age: 0 }
Person { name: 'Joe', address: 'default', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 5 }
Person { name: 'Joe', address: 'default', age: 0 }   

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

Вы также можете смешать его с требуемыми параметрами конструктора - вставьте fields в конец.

О том, как близок к стилю С#, как вам кажется, я думаю (фактический синтаксис field-init был отклонен). Я бы предпочел правильный инициализатор поля, но не похоже, что это произойдет.

Для сравнения. Если вы используете подход литья, ваш объект инициализации должен иметь ВСЕ поля для типа, на который вы производите, и не получать какие-либо специфические для класса функции (или деривации), созданные самим классом.

Ответ 3

Ниже приведено решение, которое сочетает более короткое приложение Object.assign, чтобы более точно моделировать исходный шаблон C#.

Но сначала рассмотрим предлагаемые до сих пор методы, которые включают:

  • Скопируйте конструкторы, которые принимают объект и применяют его к Object.assign
  • Умный трюк Partial<T> в конструкторе копирования
  • Использование "отливки" против POJO
  • Использование Object.create вместо Object.assign

Конечно, у каждого есть свои плюсы и минусы. Модификация целевого класса для создания конструктора копирования не всегда может быть вариантом. И "литье" теряет любые функции, связанные с типом цели. Object.create кажется менее привлекательным, поскольку для этого требуется довольно многословная карта дескриптора свойств.

Самый короткий общий ответ

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

const john = Object.assign( new Person(), {
    name: "John",
    age: 29,
    address: "Earth"
});

Что это. Единственное дополнение по шаблону C# - Object.assign вместе с 2 скобками и запятой. Ознакомьтесь с приведенным ниже рабочим примером, чтобы подтвердить, что он поддерживает прототипы функций типа. Никаких конструкторов не требуется, и никаких умных трюков.

Рабочий пример

В этом примере показано, как инициализировать объект с помощью аппроксимации инициализатора поля C#:

class Person {
    name: string = '';
    address: string = '';
    age: number = 0;

    aboutMe() {
        return `Hi, I'm ${this.name}, aged ${this.age} and from ${this.address}`;
    }
}

// typescript field initializer (maintains "type" definition)
const john = Object.assign( new Person(), {
    name: "John",
    age: 29,
    address: "Earth"
});

// initialized object maintains aboutMe() function prototype
console.log( john.aboutMe() );

Ответ 4

Вы можете воздействовать на анонимный объект, приведенный к вашему типу класса. Бонус: в визуальной студии вы получаете пользу от intellisense таким образом :)

var anInstance: AClass = <AClass> {
    Property1: "Value",
    Property2: "Value",
    PropertyBoolean: true,
    PropertyNumber: 1
};

Edit:

ПРЕДУПРЕЖДЕНИЕ Если у класса есть методы, экземпляр вашего класса не получит их. Если у AClass есть конструктор, он не будет выполнен. Если вы используете instanceof AClass, вы получите false.

В заключение, вы должны использовать интерфейс, а не класс. Наиболее распространенное использование для модели предметной области, объявленной как Простые Старые Объекты. Действительно, для модели предметной области лучше использовать интерфейс, а не класс. Интерфейсы используются во время компиляции для проверки типов, и, в отличие от классов, интерфейсы полностью удаляются во время компиляции.

interface IModel {
   Property1: string;
   Property2: string;
   PropertyBoolean: boolean;
   PropertyNumber: number;
}

var anObject: IModel = {
     Property1: "Value",
     Property2: "Value",
     PropertyBoolean: true,
     PropertyNumber: 1
 };

Ответ 5

Я предлагаю подход, который не требует Typescript 2.1:

class Person {
    public name: string;
    public address?: string;
    public age: number;

    public constructor(init:Person) {
        Object.assign(this, init);
    }

    public someFunc() {
        // todo
    }
}

let person = new Person(<Person>{ age:20, name:"John" });
person.someFunc();

ключевые моменты:

  • Typescript 2.1 не требуется, Partial<T> не требуется
  • Он поддерживает функции (по сравнению с простым утверждением типа, которое не поддерживает функции)

Ответ 6

В некоторых сценариях может быть приемлемо использовать Object.create. Ссылка Mozilla включает polyfill, если вам нужна обратная совместимость или вы хотите перевернуть свою собственную функцию инициализации.

Применяется к вашему примеру:

Object.create(Person.prototype, {
    'Field1': { value: 'ASD' },
    'Field2': { value: 'QWE' }
});

Полезные сценарии

  • Тестирование устройств
  • Объявление в строке

В моем случае я нашел это полезным в модульных тестах по двум причинам:

  • При тестировании ожиданий я часто хочу создать тонкий объект в качестве ожидания
  • Unit test Framework (например, Jasmine) могут сравнивать прототип объекта (__proto__) и не выполнять тест. Например:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails

Вывод ошибки unit test не даст подсказки о несоответствии.

  1. В модульных тестах я могу выбирать, какие браузеры я поддерживаю.

Наконец, решение, предложенное в http://typescript.codeplex.com/workitem/334, не поддерживает встроенное объявление стиля json. Например, следующее не компилируется:

var o = { 
  m: MyClass: { Field1:"ASD" }
};

Ответ 7

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

У вас могут быть свойства класса, а затем назначить их обычным способом. И, очевидно, они могут или не могут потребоваться, так что что-то еще. Это просто, что это такой хороший синтаксический сахар.

class MyClass{
    constructor(public Field1:string = "", public Field2:string = "")
    {
        // other constructor stuff
    }
}

var myClass = new MyClass("ASD", "QWE");
alert(myClass.Field1); // voila! statement completion on these properties

Ответ 8

Мне нужно решение, которое будет иметь следующее:

  • Все объекты данных являются обязательными и должны быть заполнены конструктором.
  • Не нужно указывать значения по умолчанию.
  • Можно использовать функции внутри класса.

Вот как я это делаю:

export class Person {
  id!: number;
  firstName!: string;
  lastName!: string;

  getFullName() {
    return '${this.firstName} ${this.lastName}';
  }

  constructor(data: OnlyData<Person>) {
    Object.assign(this, data);
  }
}

const person = new Person({ id: 5, firstName: "John", lastName: "Doe" });
person.getFullName();

Все свойства в конструкторе являются обязательными и не могут быть опущены без ошибки компилятора.

Он зависит от OnlyData, который отфильтровывает getFullName() из требуемых свойств, и определяется следующим образом:

// based on : https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c
type FilterFlags<Base, Condition> = { [Key in keyof Base]: Base[Key] extends Condition ? never : Key };
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
type OnlyData<T> = SubType<T, (_: any) => any>;

Текущие ограничения этого способа:

  • Требуется TypeScript 2.8
  • Классы с геттерами/сеттерами

Ответ 9

Самый простой способ сделать это - с помощью литья типов.

return <MyClass>{ Field1: "ASD", Field2: "QWE" };

Ответ 10

Если вы используете старую версию машинописного текста & lt; 2.1, тогда вы можете использовать аналогичный следующий, который в основном приведение любого к типизированному объекту:

const typedProduct = <Product>{
                    code: <string>product.sku
                };

ПРИМЕЧАНИЕ. Использование этого метода полезно только для моделей данных, поскольку оно удаляет все методы в объекте. Это в основном приведение любого объекта к типизированный объект