布尔值是最基础的数据类型,在 TypeScript 中,使用 boolean
定义布尔值类型:
let isDone: boolean = false;// 编译通过
// 后面约定,未强调编译错误的代码片段,默认为编译通过
注意:使用构造函数 Boolean
创造的对象不是布尔值:
let createdByNewBoolean: boolean = new Boolean(1);// Type 'Boolean' is not assignable to type 'boolean'.
// 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
事实上 new Boolean()
返回的是一个 Boolean
对象:
let createdByNewBoolean: Boolean = new Boolean(1);
直接调用 Boolean
也可以返回一个 boolean
类型:
let createdByBoolean: boolean = Boolean(1);
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {name: string;age?: number;[propName: string]: any;
}let tom: Person = {name: 'Tom',gender: 'male'
};
使用 [propName: string]
定义了任意属性取 string
类型的值。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {name: string;age?: number;[propName: string]: string;
}let tom: Person = {name: 'Tom',age: 25,gender: 'male'
};// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是 string
,但是可选属性 age
的值却是 number
,number
不是 string
的子属性,所以报错了。
另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' }
的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; }
,这是联合类型和接口的结合。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {name: string;age?: number;[propName: string]: string | number;
}let tom: Person = {name: 'Tom',age: 25,gender: 'male'
};
只读属性§
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly
定义只读属性:
interface Person {readonly id: number;name: string;age?: number;[propName: string]: any;
}let tom: Person = {id: 89757,name: 'Tom',gender: 'male'
};tom.id = 9527;// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,使用 readonly
定义的属性 id
初始化后,又被赋值了,所以报错了。
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
interface Person {readonly id: number;name: string;age?: number;[propName: string]: any;
}let tom: Person = {name: 'Tom',gender: 'male'
};tom.id = 89757;// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,报错信息有两处,第一处是在对 tom
进行赋值的时候,没有给 id
赋值。
第二处是在给 tom.id
赋值的时候,由于它是只读属性,所以报错了。
类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
语法§
值 as 类型
或
<类型>值
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 值 as 类型
。
形如 <Foo>
的语法在 tsx 中表示的是一个 ReactNode
,在 ts 中除了表示类型断言之外,也可能是表示一个泛型。
故建议大家在使用类型断言时,统一使用 值 as 类型
这样的语法,本书中也会贯彻这一思想。
类型断言的用途§
类型断言的常见用途有以下几种:
将一个联合类型断言为其中一个类型§
之前提到过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法:
interface Cat {name: string;run(): void;
}
interface Fish {name: string;swim(): void;
}function getName(animal: Cat | Fish) {return animal.name;
}
而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如:
interface Cat {name: string;run(): void;
}
interface Fish {name: string;swim(): void;
}function isFish(animal: Cat | Fish) {if (typeof animal.swim === 'function') {return true;}return false;
}// index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.
// Property 'swim' does not exist on type 'Cat'.
上面的例子中,获取 animal.swim
的时候会报错。
此时可以使用类型断言,将 animal
断言成 Fish
:
interface Cat {name: string;run(): void;
}
interface Fish {name: string;swim(): void;
}function isFish(animal: Cat | Fish) {if (typeof (animal as Fish).swim === 'function') {return true;}return false;
}
这样就可以解决访问 animal.swim
时报错的问题了。
需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
interface Cat {name: string;run(): void;
}
interface Fish {name: string;swim(): void;
}function swim(animal: Cat | Fish) {(animal as Fish).swim();
}const tom: Cat = {name: 'Tom',run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
上面的例子编译时不会报错,但在运行时会报错:
Uncaught TypeError: animal.swim is not a function`
原因是 (animal as Fish).swim()
这段代码隐藏了 animal
可能为 Cat
的情况,将 animal
直接断言为 Fish
了,而 TypeScript 编译器信任了我们的断言,故在调用 swim()
时没有编译错误。
可是 swim
函数接受的参数是 Cat | Fish
,一旦传入的参数是 Cat
类型的变量,由于 Cat
上没有 swim
方法,就会导致运行时错误了。
总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。