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 не даст подсказки о несоответствии.
- В модульных тестах я могу выбирать, какие браузеры я поддерживаю.
Наконец, решение, предложенное в 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
};
ПРИМЕЧАНИЕ. Использование этого метода полезно только для моделей данных, поскольку оно удаляет все методы в объекте. Это в основном приведение любого объекта к типизированный объект