【问题标题】:"Duck" Typing vs. Function Arguments in Typescript打字稿中的“鸭子”打字与函数参数
【发布时间】:2020-08-08 23:55:43
【问题描述】:

Typescript 在某些情况下执行“鸭子”输入,例如当您根据接口检查函数参数的有效性时。

例如:

interface Named {
  name: string
}

function printName(x: Named) {
  return x.name;
}

const myVar = {
  name: "John",
  happy: "OK", // This extra key-value pair does not break our printName function
};

printName(myVar);

然而,当你创建一个变量并定义它的类型时,一个额外的键值对抛出一个类型错误:

const myVar: Named = { name: "Jim", extraVal: "Oops" } // The "extraVal" is not allowed.

1) 为什么 Typescript 在第二个实例中检查 exact 匹配,但不检查传递给函数的参数?

2) 是否还有其他使用鸭式打字的情况,如何区分这些情况?

【问题讨论】:

  • { name: "John", happy: "OK", } 看起来确实实现了Named。它在 const x: T 和嵌套对象字面量创建中出错的原因是纯粹的实用主义,因为在错误的键下指定了 optional 成员。

标签: typescript


【解决方案1】:

TypeScript 的类型系统是structural(您称之为“鸭子”类型),一般来说,额外的属性不被认为违反了对象类型的结构。换句话说,TypeScript 中的对象类型是“开放/可扩展”的,而不是"closed/exact"{a: string} 类型已知具有字符串值 a 属性,但不知道缺少其他属性。

开放对象类型启用了有用的东西,例如interfaceclass 扩展,所以如果Y extends X 那么您可以在任何可以使用X 的地方使用Y,即使Y 具有更多功能。

所以回答你的第二个问题,语言中的大多数地方只依赖于结构子类型。


据我所知,编译器认为对象类型准确无误的唯一地方是当您创建新的对象字面量时。编译器假定当您创建一个对象字面量时,您关心它的所有属性。如果您随后立即将此类文字分配给不知道所有对象文字属性的类型,编译器会警告您:编译器将忘记这些额外的属性并且无法跟踪它们,这可能是您的错误部分。这称为excess property checking。只有当你有一个“新鲜”的对象字面量(尚未在任何地方分配)并且你将它分配给一个不期望其所有属性的类型时,它才会启动。

手册中给出的示例说明为什么需要进行此检查,其中涉及拼写错误的可选属性。如果您有一个类型为{ weird?: boolean } 的变量并将对象字面量{ wierd: true } 分配给它,编译器会说“嗯,这个值确实适合该类型。它没有weird 属性,这很好,因为它是可选的。但它有这个额外的 wierd 属性,我会立即忘记;为什么有人会这样做?也许这是一个错误。我不知道你是否同意这个推理,但确实如此。


所以回答你的第一个问题,编译器很满意

const myVar = {
  name: "John",
  happy: "OK"
};    
printName(myVar);

因为对象字面量在其初始赋值中并未扩展(已知myVar 的类型同时具有namehappy 属性),并且当您将其传递给printName() 时,它不再是“新鲜的”。编译器不会知道printName() 实现中的happy 属性,但它确实知道myVar 中的happy 属性。

它不满意

const myVar: Named = { name: "Jim", happy: "OK" };

因为它被过多的属性检查所捕获。 myVar 的类型不会包含对 happy 的任何引用。


好的,希望对您有所帮助;祝你好运!

【讨论】:

  • TLDR, "据我所知,编译器唯一表现对象类型准确的地方是当你创建一个新的对象字面量时......这称为多余的属性检查。它只当你有一个“新鲜”的对象字面量(尚未在任何地方分配)并且你将它分配给一个不期望其所有属性的类型时,它就会起作用。”整个帖子都很棒,谢谢!
【解决方案2】:

除了@jcalz 的惊人回答,还有两种绕过检查的打字方式:

const myVar: Named & any = { name: "Jim", extraVal: "Oops" };

const myVar: Named = { name: "Jim", extraVal: "Oops" } as Named;

【讨论】:

  • & 语法的名称是什么?
  • @HarryCramer 路口。具体来说,T & U 计算的类型据说是TU 的交集。
  • 请注意,交集Named & any 等价于any,编译器会忘记Named。也就是说,const myVar: Named & anyconst myVar: any 之间没有区别...导致像 this 这样的怪异;所以我不推荐& any,除非你打算完全关闭类型检查。类型断言 (as Named) 更好,但由于类型断言允许您缩小 以及 加宽 类型,因此您最终会遇到潜在的不安全操作,如 @ 987654322@,所以要小心。
猜你喜欢
  • 1970-01-01
  • 2020-02-13
  • 2019-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-25
  • 2021-09-06
  • 1970-01-01
相关资源
最近更新 更多