为什么 Typescript 无法识别我代码中的类型?
Why can Typescript not figure out the type in my code?
为什么 Typescript 编译器会抱怨以下代码?
type Foo = {
a: string
}
type Bar = {
b: number
}
type Baz = Foo | Bar;
function f(x: Baz): number {
if (x.a) { // property 'a' does not exist on type Bar!
return 0;
}
if (x.b) { // property 'b' does not exist on type Foo!
return 1;
}
return -1;
}
为什么编译器不能(或不会)允许那些 属性 访问
请考虑此 github thread linked in the comments by jcalz 中提到的以下情况:
interface Vec2 {
x: number
y: number
}
interface Vec3 {
x: number
y: number
z: number
}
const m = { x: 0, y: 0, z: "hello world" };
const n: Vec2 = m; // N.B. structurally m qualifies as Vec2!
function f(x: Vec2 | Vec3) {
if (x.z) return x.z.toFixed(2); // This fails if z is not a number!
}
f(n); // compiler must allow this call
代码的作者在这里做出了一个不幸的假设,即仅仅因为 属性 存在并且真实 它是某种类型。但这是 双重 错误:你可能有一个正确类型的假值(在这种情况下为零或 NaN)或不同类型的真值。
但是还有更微妙的陷阱:
type Message =
{ kind: "close" } |
{ kind: "data", payload: object }
function handle(m: Message) {
switch (m.kind) {
case "close":
console.log("closing!");
// forgot 'break;' here
case "data":
updateBankAccount(m.payload);
}
}
在这种情况下,您希望 编译器抱怨意外的 属性 访问,而不仅仅是静默传播 undefined
。捕捉这类事情是我们首先使用静态分析的重要原因。
Typescript 编译器已经是一项了不起的工程,它不仅在动态语言而且在 ultra 动态语言之上分层静态类型系统。您在此处查找的内容称为 type narrowing,您可以在其中获取可能不止一种类型的值,然后将其缩小为特定类型。 TS 编译器支持(至少)五种不同的习语来实现这一点:
instanceof
运算符。
typeof
运算符。
in
运算符。
- 一个user-defined type guard.
- 一个discriminated union.
让我们依次看一下:
instanceof
这一个适用于用户定义 类:
class A {
public a: number
constructor () {
this.a = 4;
}
}
class B {
public b: number
constructor () {
this.b = 5;
}
}
type AB = A | B;
function abba(x: AB): number {
if (x instanceof A) return x.a;
if (x instanceof B) return x.b;
return 0;
}
类型
这个适用于 JS 基元(未定义、数字、字符串、布尔值等)。
type snumber = string | number;
function f(x: snumber): string {
if (typeof x === 'number') {
return x.toFixed(2); // strings don't have toFixed
} else {
return x.repeat(2); // numbers don't have repeat
}
}
在
这个适用于结构类型对象:
type A = {
a: number
}
type B = {
b: string
}
type AB = A | B;
function f(x: AB): number {
if ('a' in x) return x.a;
if ('b' in x) return 5;
return 0;
}
精明的 reader 会注意到这与上面第一个激励示例存在相同的问题,即对象上 属性 的存在并不能以任何方式保证类型。这是 TS 团队的务实决定,允许 想要 获得值或 undefined
的简单选择加入习语的不常见行为,等等就像演员表是一个隐含的承诺,即程序员对可能的结果负责。
用户定义的类型保护
这几乎适用于任何事情,但比之前的选项更冗长。这个直接来自 TS Handbook:
function isFish(pet: Fish | Bird): pet is Fish { // note the 'is'
return (pet as Fish).swim !== undefined;
}
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
歧视工会
当您有一堆非常相似的对象,这些对象仅在单个 属性:
的(静态可知!)值上有所不同时,此方法效果最佳
type A = {
a: string
kind: 'is-an-a'
}
type B = {
b: number
kind: 'is-a-b'
}
type AB = A | B;
function f(x: AB): string {
switch (x.kind) {
case 'is-an-a': return x.a;
case 'is-a-b': return '' + x.b;
}
}
请注意,正如我所说,您需要使判别式(在本例中为 kind
属性)静态可知值,通常是字符串文字或枚举成员。您不能使用变量,因为它们的值在编译时是未知的。
所以总而言之,Typescript 编译器 可以 弄清楚,你只需要使用一个它可以静态验证的习惯用法,而不是它不能静态验证的习惯用法,它给你相当多的选择。
为什么 Typescript 编译器会抱怨以下代码?
type Foo = {
a: string
}
type Bar = {
b: number
}
type Baz = Foo | Bar;
function f(x: Baz): number {
if (x.a) { // property 'a' does not exist on type Bar!
return 0;
}
if (x.b) { // property 'b' does not exist on type Foo!
return 1;
}
return -1;
}
为什么编译器不能(或不会)允许那些 属性 访问
请考虑此 github thread linked in the comments by jcalz 中提到的以下情况:
interface Vec2 {
x: number
y: number
}
interface Vec3 {
x: number
y: number
z: number
}
const m = { x: 0, y: 0, z: "hello world" };
const n: Vec2 = m; // N.B. structurally m qualifies as Vec2!
function f(x: Vec2 | Vec3) {
if (x.z) return x.z.toFixed(2); // This fails if z is not a number!
}
f(n); // compiler must allow this call
代码的作者在这里做出了一个不幸的假设,即仅仅因为 属性 存在并且真实 它是某种类型。但这是 双重 错误:你可能有一个正确类型的假值(在这种情况下为零或 NaN)或不同类型的真值。 但是还有更微妙的陷阱:
type Message =
{ kind: "close" } |
{ kind: "data", payload: object }
function handle(m: Message) {
switch (m.kind) {
case "close":
console.log("closing!");
// forgot 'break;' here
case "data":
updateBankAccount(m.payload);
}
}
在这种情况下,您希望 编译器抱怨意外的 属性 访问,而不仅仅是静默传播 undefined
。捕捉这类事情是我们首先使用静态分析的重要原因。
Typescript 编译器已经是一项了不起的工程,它不仅在动态语言而且在 ultra 动态语言之上分层静态类型系统。您在此处查找的内容称为 type narrowing,您可以在其中获取可能不止一种类型的值,然后将其缩小为特定类型。 TS 编译器支持(至少)五种不同的习语来实现这一点:
instanceof
运算符。typeof
运算符。in
运算符。- 一个user-defined type guard.
- 一个discriminated union.
让我们依次看一下:
instanceof
这一个适用于用户定义 类:
class A {
public a: number
constructor () {
this.a = 4;
}
}
class B {
public b: number
constructor () {
this.b = 5;
}
}
type AB = A | B;
function abba(x: AB): number {
if (x instanceof A) return x.a;
if (x instanceof B) return x.b;
return 0;
}
类型
这个适用于 JS 基元(未定义、数字、字符串、布尔值等)。
type snumber = string | number;
function f(x: snumber): string {
if (typeof x === 'number') {
return x.toFixed(2); // strings don't have toFixed
} else {
return x.repeat(2); // numbers don't have repeat
}
}
在
这个适用于结构类型对象:
type A = {
a: number
}
type B = {
b: string
}
type AB = A | B;
function f(x: AB): number {
if ('a' in x) return x.a;
if ('b' in x) return 5;
return 0;
}
精明的 reader 会注意到这与上面第一个激励示例存在相同的问题,即对象上 属性 的存在并不能以任何方式保证类型。这是 TS 团队的务实决定,允许 想要 获得值或 undefined
的简单选择加入习语的不常见行为,等等就像演员表是一个隐含的承诺,即程序员对可能的结果负责。
用户定义的类型保护
这几乎适用于任何事情,但比之前的选项更冗长。这个直接来自 TS Handbook:
function isFish(pet: Fish | Bird): pet is Fish { // note the 'is'
return (pet as Fish).swim !== undefined;
}
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
歧视工会
当您有一堆非常相似的对象,这些对象仅在单个 属性:
的(静态可知!)值上有所不同时,此方法效果最佳type A = {
a: string
kind: 'is-an-a'
}
type B = {
b: number
kind: 'is-a-b'
}
type AB = A | B;
function f(x: AB): string {
switch (x.kind) {
case 'is-an-a': return x.a;
case 'is-a-b': return '' + x.b;
}
}
请注意,正如我所说,您需要使判别式(在本例中为 kind
属性)静态可知值,通常是字符串文字或枚举成员。您不能使用变量,因为它们的值在编译时是未知的。
所以总而言之,Typescript 编译器 可以 弄清楚,你只需要使用一个它可以静态验证的习惯用法,而不是它不能静态验证的习惯用法,它给你相当多的选择。