这样做有什么区别
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()
}
答案 0 :(得分:10)
正如JB Nizet非常正确指出的那样,HTTP请求产生的反序列化JSON值永远不会是类的实例。
虽然TypeScript中class
构造的双重角色(见下文)可以使用class
来描述这些响应值的形状,但这是一种不好的做法,因为响应文本将被反序列化为纯JavaScript对象。
请注意,在这个答案中,我没有使用Comment.likes
的类型,因为在问题中你将它作为string
,但对我来说感觉就像number
,所以我把它留给了读者。
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() {
const [topComment] = this.comments.slice().sort((x, y) => y < x);
// since there might not be any comments, it is likely that a check will be made here
if (topComment instanceof Comment) { // always false at runtime
return topComment;
}
}
由于注释实际上不是Comment
类的实例,因此上述检查将始终失败,因此代码中存在错误。但是,typescript不会捕获错误,因为我们说comments
是Comment
类的一个实例数组,这将使检查有效(回想一下response.json()
返回{{1可以在没有警告的情况下转换为任何类型,因此在编译时一切正常。
但是,如果我们已将评论声明为any
interface
然后interface Comment {
comment: string;
likes;
}
将继续键入check,因为它实际上是正确的代码,但getComments
将在编译时在if语句中引发错误,因为正如许多其他人所指出的那样getTopComment
作为仅编译时构造的{1}}不能用作执行interface
检查的构造函数。编译器会告诉我们我们有错误。
<强>说明:强>
除了给出的所有其他原因之外,在我看来,当你在JavaScript / TypeScript中有一些代表普通旧数据的东西时,使用类通常是矫枉过正的。它创建了一个带有原型的函数,并且有许多我们不太可能不需要或不关心的其他方面。
如果你使用对象,它也会抛弃你默认获得的好处。这些好处包括用于创建和复制对象的语法糖以及TypeScript对这些对象类型的推断。
考虑
instanceof
如果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, comment: string) {
const commentService = new CommentService();
await commentService.createCommnet({comment, likes});
}
是一个类,我需要通过Comment
的{{1}}来引入一些样板。当然,这也增加了耦合。
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);
}
简而言之,有许多理由使用Comment
来描述响应的类型以及使用TypeScript时与HTTP服务交互的请求。
注意:您还可以使用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 (Number(likes) < 0 || !Number.isSafeInteger(Number(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
周围的工具通常会使其更受欢迎场景。
答案 1 :(得分:7)
与大多数其他OOP语言一样:对于类,您可以创建实例(通过其构造函数),而不能创建接口实例。
换句话说:如果您只返回反序列化的JSON,那么使用该接口以避免混淆是有意义的。让我们假设您向foo
类添加了一些方法Comment
。如果声明register
方法返回Comment
,那么您可能会认为可以在寄存器的返回值上调用foo
。但是这不会起作用,因为register
有效返回的只是反序列化的JSON而没有在foo
类上实现Comment
。更具体地说,它不是您的Comment
类的实例。当然,您也可能会在foo
中意外声明CommentInterface
方法但它仍然不起作用,但是foo
方法没有实际代码正在执行,使您更容易推断调用foo
无法正常工作的根本原因。
另外在语义层面上考虑它:声明返回一个接口保证在接口上声明的所有内容都存在于返回值上。声明返回一个类实例保证你......好吧......返回一个类实例,这不是你正在做的事情,因为你要返回反序列化的Json。