8

这样做有什么区别

export class Comment {
  likes: string;
  comment: string;

  constructor(likes: string, comment: string){
    this.comment = comment;
    this.likes = likes;
  }
}

和这个

export interface CommentInterface {
  likes: string;
  comment: string;
}

关于声明可观察类型

register: Observable<CommentInterface[]> {
    return this.http.get()
}
4

2 回答 2

12

正如 JB Nizet 非常正确地指出的那样,HTTP 请求产生的反序列化 JSON 值永远不会是类的实例。

虽然 TypeScript 中构造的双重角色(见下文)class使得可以使用 aclass来描述这些响应值的形状,但这是一种糟糕的做法,因为响应文本将被反序列化为纯 JavaScript 对象。

JavaScript 和 TypeScript 中的类声明:

在 JavaScript 中,类声明

class Comment {
  constructor(likes, comment) {
    this.likes = likes;
    this.comment = comment;
  }
}

创建一个可以实例化的值,new以充当本质上的工厂。

在 TypeScript 中,类声明创建了件事。

第一个是与上述完全相同的 JavaScript 类。第二种是描述通过编写创建的实例的结构的类型

new Comment(4, 'I love your essays')

第二个工件,类型,然后可以用作类型注释,例如在您的示例中

register(): Observable<Comment[]> {
    return this.http.get()
}

它表示register返回一个实例Observable数组。Comment class

现在假设您的 HTTP 请求返回以下 JSON

[
  {
    "likes": 4,
    "comment": "I love you oh so very much"
  },
  {
    "likes": 1,
    "comment": "I lust after that feeling of approval that only likes can bring"
  }
]

但是方法声明

register(): Observable<Comment[]>;

虽然它正确地允许调用者写

register().subscribe(comments => {
  for (const comment of comment) {
    if (comment.likes > 0) {
      likedComments.push(comment);
    }
  }
});

这一切都很好,不幸的是它还允许调用者编写代码

getComments() {
  register().subscribe(comments => {
    this.comments = comments;
  });
}

getTopComment() {
  // since there might not be any comments
  // it is likely that a check will be made here
  const [topComment] = this.comments.slice().sort((x, y) => y - x);

  // BUG! Always false at runtime.
  if (topComment instanceof Comment) {
    return topComment;
  }
}

由于注释实际上不是Comment该类的实例,因此上述检查将始终失败,因此代码中存在错误。但是,TypeScript 不会捕获错误,因为我们说过这comments是一个Comment类的实例数组,这将使检查有效(回想一下,可以在没有警告的情况下将response.json()返回any值转换为任何类型,因此在编译时一切看起来都很好)。

但是,如果我们将评论声明为interface

interface Comment {
  comment: string;
  likes: number;
}

thengetComments将继续进行类型检查,因为它实际上是正确的代码,但getTopComment会在 if 语句中在编译时引发错误,因为正如许多其他人所指出的那样,作为interface仅编译时的构造,不能像 if 一样使用它是执行instanceof检查的构造函数。编译器会告诉我们有一个错误。


评论:

除了给出的所有其他原因之外,在我看来,当你有一些表示JavaScript/TypeScript 中普通旧数据的东西时,使用类通常是矫枉过正的。它创建了一个带有原型的函数,并有许多我们可能不需要或关心的其他方面。

如果您使用对象,它还会丢弃您默认获得的好处。这些好处包括用于创建和复制对象的语法糖以及 TypeScript 对这些对象类型的推断。

考虑

import Comment from 'app/comment';

export default class CommentService {    
  
  async getComments(): Promse<Array<Comment>> {
    const response = await fetch('api/comments', {httpMethod: 'GET'});
    const comments = await response.json();
    return comments as Comment[]; // just being explicit.
  }

  async createComment(comment: Comment): Promise<Comment> {
    const response = await fetch('api/comments', {
      httpMethod: 'POST',
      body: JSON.stringify(comment)
    });
    const result = await response.json();
    return result as Comment; // just being explicit.
  }
}

如果Comment是一个接口,我想使用上面的服务来创建评论,我可以这样做如下

import CommentService from 'app/comment-service';

export async function createComment(likes: number, comment: string) {    
  const commentService = new CommentService();
  
  await commentService.createCommnet({comment, likes});
}

如果Comment是一个类,我需要引入一些样板,需要import. Comment当然,这也增加了耦合。

import CommentService from 'app/comment-service';
import Comment from 'app/comment';

export async function createComment(likes, comment: string) {    
  const commentService = new CommentService();
  
  const comment = new Comment(likes, comment); // better get the order right

  await commentService.createCommnet(comment);
}

那是两行额外的行,其中一行涉及依赖另一个模块来创建一个对象。

现在 if是一个接口,但我想要一个复杂的类,在我将它提供给我的服务之前进行Comment验证等等,我仍然可以拥有它。

import CommentService from 'app/comment-service';
import Comment from 'app/comment';

// implements is optional and typescript will verify that this class implements Comment
// by looking at the definition of the service method so I could remove it and 
// also remove the import statement if I wish
class ValidatedComment implements Comment {
  constructor(public likes, public comment: string) {
    if (likes < 0 || !Number.isSafeInteger(likes)) {
      throw RangeError('Likes must be a valid number >= 0'
    }
  }
}

export async function createComment(likes, comment: string) {
  const commentService = new CommentService();
  
  const comment = new ValidatedComment(likes, comment); // better get the order right

  await commentService.createCommnet(comment);
}

简而言之,interface使用 TypeScript 来描述响应的类型以及与 HTTP 服务交互的请求有很多原因。

注意:您也可以使用type声明,它同样安全和健壮,但不那么惯用,并且周围的工具interface通常使它更适合这种情况。

于 2017-07-30T22:16:00.757 回答
8

与大多数其他 OOP 语言一样:对于类,您可以创建实例(通过它们的构造函数),而您不能创建接口实例。

换句话说:如果你只是返回反序列化的 JSON,那么使用接口以避免混淆是有意义的。让我们假设您在类中添加了一些foo方法Comment。如果您的register方法被声明为返回 aComment那么您可能会假设您可以调用fooregister 的返回值。但这不会起作用,因为register有效返回的只是反序列化的 JSON,而您fooComment类上没有实现。更具体地说,它不是您的Comment类的实例。当然,您也可能不小心foo在您的方法中声明了该方法CommentInterface,但它仍然无法正常工作,但随后将没有实际的代码foo只是没有被执行的方法,从而更容易推断出调用foo不工作的根本原因。

另外在语义层面上考虑一下:声明返回接口保证接口​​上声明的所有内容都存在于返回值中。声明返回一个类实例保证你......嗯......返回一个类实例,这不是你正在做的,因为你正在返回反序列化的 Json。

于 2017-07-30T21:21:52.603 回答