- type annotation 类型注解,
- type inference 类型推断, TS会自动去尝试分析变量的类型。 通常, 声明和赋值在同一行时能够自动推断。
类型推断
声明时赋值
可推断:声明时赋值。
不可推断: 先声明, 再赋值// let count:number = 3;
let count = 3;
let count;
count = 3;
类的实例化
// const date:Date = new Date();
const date = new Date();
特殊的函数返回值
函数中, 有时也能自动推断, 如返回值是parseInt()
// const func = (str: string): number => {
const func = (str: string) => {
return parseInt(str, 10); // 依据parseInt能够推断出返回类型为number
}
断言
有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
类型断言有两种形式:
value - (value as string)
3.1 “尖括号” 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
console.log((<string>someValue) ? true : false) // true
3.2 as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
console.log((someValue as string) ? true : false) // true
类型守卫
A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— TypeScript 官方文档
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。
类型保护:
- 断言
- in
- typeof
- instanceof
断言
4.1 in 关键字
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
消除错误提示
interface Bird {
fly: boolean;
sing: () => {};
}
interface Dog {
fly: boolean;
bark: () => {};
}
function trainAnial(animal: Bird | Dog) {
if('sing' in animal) {
animal.sing()
}
}
4.2 typeof 关键字
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof
类型保护只支持两种形式:typeof v === "typename"
和 typeof v !== typename
,"typename"
必须是 "number"
, "string"
, "boolean"
或 "symbol"
。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
消除错误提示
function add(x: string | number, y: string | number) {
if (typeof x === 'string' || typeof y === 'string') {
return `${x}${y}`
}
return x + y
}
4.3 instanceof 关键字
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
消除错误提示
class NumberObj {
count: number
}
function add(x: object | NumberObj, y: object | NumberObj) {
if (x instanceof NumberObj && y instanceof NumberObj) {
return x.count + y.count
}
return 0
}
4.4 自定义类型保护的类型谓词
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
联合类型和类型别名
5.1 联合类型
联合类型通常与 null
或 undefined
一起使用:
const sayHello = (name: string | undefined) => {
/* ... */
};
例如,这里 name
的类型是 string | undefined
意味着可以将 string
或 undefined
的值传递给sayHello
函数。
sayHello("Semlinker");
sayHello(undefined);
通过这个示例,你可以凭直觉知道类型 A 和类型 B 联合后的类型是同时接受 A 和 B 值的类型。
5.2 可辨识联合
TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
1.可辨识
可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
}
在上述代码中,我们分别定义了 Motorcycle
、 Car
和 Truck
三个接口,在这些接口中都包含一个 vType
属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
2.联合类型
基于前面定义了三个接口,我们可以创建一个 Vehicle
联合类型:
type Vehicle = Motorcycle | Car | Truck;
现在我们就可以开始使用 Vehicle
联合类型,对于 Vehicle
类型的变量,它可以表示不同类型的车辆。
3.类型守卫
下面我们来定义一个 evaluatePrice
方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下:
const EVALUATION_FACTOR = Math.PI;
function evaluatePrice(vehicle: Vehicle) {
return vehicle.capacity * EVALUATION_FACTOR;
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);
对于以上代码,TypeScript 编译器将会提示以下错误信息:
Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.
原因是在 Motorcycle 接口中,并不存在 capacity
属性,而对于 Car 接口来说,它也不存在 capacity
属性。那么,现在我们应该如何解决以上问题呢?这时,我们可以使用类型守卫。下面我们来重构一下前面定义的 evaluatePrice
方法,重构后的代码如下:
function evaluatePrice(vehicle: Vehicle) {
switch(vehicle.vType) {
case "car":
return vehicle.transmission * EVALUATION_FACTOR;
case "truck":
return vehicle.capacity * EVALUATION_FACTOR;
case "motorcycle":
return vehicle.make * EVALUATION_FACTOR;
}
}
在以上代码中,我们使用 switch
和 case
运算符来实现类型守卫,从而确保在 evaluatePrice
方法中,我们可以安全地访问 vehicle
对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。
类型别名
类型别名用来给一个类型起个新名字。
复杂对象元素
let objectArr: { name: string, age: number }[] = [
{
name: 'dell',
age: 28
}
];
// [ { name: 'dell', age: 28 } ]
// 类型别名
type User = { name: string, age: number };
type User = { name: string; age: number };
let objectArr: User [] = [
{
name: 'dell',
age: 28
}
];
// [ { name: 'dell', age: 28 } ]
class替代类型别名
class User {
name: string;
age: number;
constructor() {
this.name = '';
this.age = 6;
}
}
let objectArr: User[] = [
new User(),
{
name: 'dell',
age: 28
}
];
// [ User { name: '', age: 6 }, { name: 'dell', age: 28 } ]
用于函数参数
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
交叉类型
TypeScript 交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
interface IPerson {
id: string;
age: number;
}
interface IWorker {
companyId: string;
}
type IStaff = IPerson & IWorker;
const staff: IStaff = {
id: 'E1006',
age: 33,
companyId: 'EFT'
};
console.dir(staff)
在上面示例中,我们首先为 IPerson 和 IWorker 类型定义了不同的成员,然后通过 &
运算符定义了 IStaff 交叉类型,所以该类型同时拥有 IPerson 和 IWorker 这两种类型的成员。
校验
强校验
- 函数参数