Простые объекты VS экземпляры класса для объектов модели
Какова лучшая практика для создания объектов модели в Angular / TypeScript:
Должен ли я использовать аннотацию типа с нотацией объекта (объекты являются простыми экземплярами
Object
)? Напримерlet m: MyModel = { name: 'foo' }
Должен ли я использовать
new
оператор (объекты являются экземплярами соответствующего прототипа)?Следует ли смешивать эти два подхода и использовать их ситуативно? (Например, простые объекты при получении ответа от
HttpClient
, ноnew MyModel('foobar')
для удобства создания экземпляров путем передачи свойств в качестве аргументов конструктора)
История вопроса
Я новичок в TypeScript и, как и многие другие разработчики, я из популярных объектно-ориентированных языков, таких как Java.
Основная концепция, которую я понял, заключается в том, что аннотации типов в TypeScript не играют роли во время выполнения. Они важны для компилятора и для автоматического завершения вашего редактора. По сути, это ECMAScript с добавлением проверок типов времени компиляции.
В то время, когда я не знал об этом и ожидал, что TypeScript будет своего рода "клиентской Java", я обнаружил этот отчет об ошибке Angular: https://github.com/angular/angular/issues/20770
Люди (которые не понимают концепции типов в TypeScript) жалуются на HttpClient
не преобразовывать возвращенный простой объект в тип их модели. Другие люди защищают это поведение и указывают, что в JavaScript нет типов времени выполнения.
И здесь возникает моя проблема: На самом деле есть типы времени выполнения в JavaScript. Вы можете создавать экземпляры прототипов с помощью new
оператор и даже проверить их конструктор с instanceof
оператор.
В TypeScript у вас есть два способа создания экземпляра:
1) Используйте обозначение объекта (как показано в уроке Angular):
hero: Hero = {
id: 1,
name: 'Windstorm'
};
2) Используйте new
-оператором:
hero: Hero = new Hero();
В данный момент я работаю над проектом, в котором смешаны эти два варианта. Т.е. объекты модели одного типа являются экземплярами Object
в некоторых случаях и случаях Hero
в других случаях.
Я ожидаю, что это приведет к проблемам позже, потому что конструктор вызывается только в последнем случае.
Моя идея для правила / наилучшей практики состояла в том, чтобы определить все экземпляры модели как простые объекты и использовать конструктор для служб, компонентов и т. Д., Которые создаются путем внедрения зависимостей. В результате я бы не стал использовать new
оператор на всех.
Однако я не уверен, что это хорошая идея, и я не смог найти рекомендации по этому поводу.
РЕДАКТИРОВАТЬ
Важное примечание для близких избирателей: я не ищу ваше личное мнение здесь. Я скорее ищу какую-нибудь официально документированную лучшую практику Angular, так как считаю, что это принципиальное проектное решение, которое должно быть принято с самого начала проекта, и эти подходы не должны смешиваться случайным образом без конкретной причины. Может быть, ответ прост: "Нет официальной рекомендации, какое решение принять".
2 ответа
На мой взгляд, вы должны использовать new
оператор для создания новых объектов вашего класса, если вам нужно выполнить сложные операции над самим объектом.
Если вам нужен только доступ к свойствам, вы можете использовать литерал объекта для создания нового объекта.
Это имеет такие преимущества, как инкапсуляция, наследование и т. Д., К которым разработчик из Java-фона мог бы лучше относиться.
Если вы непосредственно назначаете объект, как показано ниже, вам необходимо явно установить в нем функции, например: getName
функция.
hero: Hero = {
id: 1,
name: 'Windstorm',
getName: function(){ // returns name }
};
Тем не менее, определив класс Hero
с функцией getName
а затем создавая экземпляр этого класса, вы автоматически получите это function
независимо от того, сколько раз вы создаете экземпляры.
Для лучшего понимания объектно-ориентированного JavaScript, вы можете перейти по ссылке ниже: -
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS
Что касается вопроса ниже,
"Люди (которые не понимают концепцию типов в TypeScript) жалуются на то, что HttpClient не преобразует возвращаемый простой объект в тип своего модельного класса".
HttpClient просто возвращает объект, который отправляет сервер, он находится на JS, чтобы преобразовать его в требуемую форму. Вы всегда можете отобразить ответ в требуемый экземпляр модели, используя его конструктор, который можно определить, как показано ниже:
constructor(hero){
this.id = hero.id;
this.name = hero.name;
}
и вы можете отобразить массив, возвращенный с сервера, как показано ниже:
map(hero => new Hero(hero))
На самом деле есть типы времени выполнения в JavaScript.
Вид. Значение может иметь определенный тип, однако переменные и свойства не имеют привязанных к ним типов. Тем не менее, если вы вводите сетевые запросы и т. Д.
fetch("someurl").then(res => res.json()) as Promise<IUser>
и этот сетевой запрос неожиданно возвращает что-то еще, кроме пользователя, ничего не произойдет, так как типы Typescript не существуют во время выполнения. Однако это поведение может быть легко добавлено с помощью typeguards:
function isUser(user: any) : user is IUser {
return Number.isInteger(user.id) && typeof user.name === "string";
}
который позволяет выполнять проверки типов во время выполнения:
const result = await fetch("someurl");
if(!isUser(result)) throw new Error();
// result is definetly a IUser here
Должен ли я использовать наследство или нет?
Это вопрос предпочтений. Однажды я написал приложение, которое интенсивно работает с базой данных, поэтому мне пришлось много заниматься сериализацией / десериализацией, и меня очень раздражало то, что я всегда переносил ответ из базы данных в экземпляр класса. Поэтому для моих моделей БД я начал использовать следующий шаблон:
type User = {
name: string;
id: number;
};
const User = {
get(id: number): User { /*...*/ }
changeName(user: User, name: string) { /*..*/ }
};
Это позволило мне написать:
const admin: User = User.get(123);
И я мог просто делать Typecast на БД, и у меня обоих была безопасность типов и хорошие API.
Является ли это хорошим шаблоном для вашего сценария использования или нет, на самом деле зависит от того, сколько сериализации / десериализации вы делаете.