【问题标题】:plain objects VS class instances for model objects普通对象 VS 模型对象的类实例
【发布时间】:2018-08-08 08:40:54
【问题描述】:

在 Angular / TypeScript 中创建模型对象的最佳实践是什么:

  1. 我应该将类型注释与对象表示法一起使用(对象是Object 的普通实例)吗?例如。 let m: MyModel = { name: 'foo' }

  2. 我应该使用new 运算符(对象是各自原型的实例)吗?

  3. 是否应该混合使用这两种方法并根据情况使用? (例如普通对象,当接收到来自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 tutorial 中所示):

hero: Hero = {
  id: 1,
  name: 'Windstorm'
};

2) 使用new-运算符:

hero: Hero = new Hero();

目前我正在处理一个混合了这两个选项的项目。 IE。相同类型的模型对象在某些情况下是Object 的实例,在其他情况下是Hero 的实例。

我预计这会导致以后出现问题,因为构造函数只在后一种情况下被调用。

我对规则/最佳实践的想法是将所有模型实例定义为普通对象,并将构造函数用于依赖注入创建的服务、组件等。因此,我根本不会使用 new 运算符。

但我不确定这是否是个好主意,也找不到关于它的最佳实践建议。

编辑

给亲密选民的重要提示:我不是在这里寻找您的个人意见。我宁愿寻找某种官方记录的 Angular 最佳实践,因为我认为这是一个核心设计决策,必须从项目开始就做出,并且这些方法不应该在没有特定原因的情况下随意混合。也许答案只是一个简单的“没有官方建议做出哪个决定”。

【问题讨论】:

  • 我个人对此事的看法:如果您使用对象中的函数,请创建一个类的实例。如果您只处理属性,请将您的答案作为界面输入。
  • 我还发现包含嵌套对象数组的模型对象存在问题。使用构造函数,您可以提供一个空数组。普通对象的属性将是 undefined 并且运行时异常的可能性更高
  • 这不是普通对象的问题,而是您自己的代码的问题。这就像说类存在问题,因为如果您不在构造函数中编写它,它不会创建空数组。
  • 我的评论是对你的评论的补充。数组属性是真实实例优于普通对象的另一种情况。
  • 我知道,我正在纠正你所说的,因为我发现它不真实。同样,类实例与普通对象相比“没有优势”:两者处理数组的方式相同。

标签: javascript angular typescript prototypal-inheritance


【解决方案1】:

在我看来,如果您需要对对象本身执行复杂的操作,您应该使用new 运算符来创建您的类的新对象。

如果你只需要访问属性,你可以使用对象字面量来创建一个新对象。

这具有封装、继承等优点,来自 Java 背景的开发人员可以更好地理解这些优点。

如果你像下面这样直接分配一个对象,那么你必须在其中显式设置函数,例如getName函数。

hero: Hero = {
  id: 1,
  name: 'Windstorm',
  getName: function(){ // returns name }
};

但是,通过使用函数 getName 定义类 Hero,然后创建该类的实例,无论创建多少次实例,您都将自动获得该 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))

【讨论】:

  • 我很欣赏你的意见,但是我正在寻找官方的最佳实践,或者想知道是否有一种典型的处理方式,因为随着时间的推移,几乎每个人都被证明是最佳实践正在这样做。就像,“goto is evil”是一个既定的建议。我不想在这里开始讨论,因为 SO 是错误的地方。如果没有既定的最佳实践,我将在论坛/新闻组中开始讨论。
  • 没有官方文档说明创建对象的最佳实践。但是,这完全取决于开发人员和他的背景。使用classesnew 运算符只会使应用程序更易于理解,例如Java 开发人员。事实上,使用new 运算符,您可以利用多种可能性,可能在增强您的应用程序的同时,当您可能必须为该对象/模型编写函数时。
【解决方案2】:

事实上,JavaScript 中有运行时类型。

有点。一个值可以有特定的类型,但是变量和属性没有绑定到它们的类型。也就是说,如果您键入网络请求等。

  fetch("someurl").then(res => res.json()) as Promise<IUser>

并且该网络请求突然返回用户以外的其他内容,因为 Typescript 类型在运行时不存在,所以什么都不会失败。但是,可以使用 typeguard 轻松添加此行为:

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

而且我可以简单地在 db 上进行类型转换,并且我都有类型安全和不错的 API。

这对于您的用例是否是一个好的模式实际上取决于您执行了多少序列化/反序列化。

【讨论】:

    猜你喜欢
    • 2016-02-20
    • 1970-01-01
    • 2017-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 2021-05-04
    相关资源
    最近更新 更多