联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种
TypeScript 的类型系统允许我们使用多种运算符,从现有类型中构建新类型
使用第一种组合类型的方法:联合类型(Union Type)
- 联合类型是由两个或者多个其他类型组成的类型
- 表示可以是这些类型中的任何一个值
- 联合类型中的每一个类型被称之为联合成员(union’s members)
需求 1: 定义一个函数得到一个数字或字符串值的字符串形式值
1 2 3
| function toString2(x: number | string): string { return x.toString(); }
|
需求 2: 定义一个函数得到一个数字或字符串值的长度
1 2 3 4 5 6 7 8 9 10
| function getLength(x: number | string) {
if (x.length) { return x.length; } else { return x.toString().length; } }
|
处理多种类型问题
- 需要使用缩小(narrow)联合
- TypeScript 可以根据我们缩小的代码结构,推断出更加具体的类型
1 2 3 4 5 6 7
| function getLength(x: number | string) { if (typeof x === "string") { return x.length; } else { return x.toString().length; } }
|
类型断言
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构
它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查
类型断言有两种形式:其一是“尖括号”语法;另一个为 as
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
function getLength(x: number | string) { if ((<string>x).length) { return (x as string).length; } else { return x.toString().length; } } console.log(getLength("abcd"), getLength(1234));
|
1 2 3 4 5 6 7 8 9
| const age: number = 18;
const age3 = age as any; const age4 = age3 as string; console.log(age4.split(" "));
|
类型推断
类型推断: TS 会在没有明确的指定类型的时候推测出一个类型,有下面 2 种情况:
- 定义变量时赋值了, 推断为对应的类型
- 定义变量时没有赋值, 推断为 any 类型
1 2 3 4 5 6 7 8
| let b9 = 123;
let b10; b10 = 123; b10 = "abc";
|
类型别名
以前通过在类型注解中编写 对象类型 和 联合类型,如果想要多次在其他地方使用时,就要编写多次,就可以给对象类型起一个别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| type MyNumber = number; const age: MyNumber = 18;
type IDType = number | string;
function printID(id: IDType) { console.log(id); }
type PointType = { x: number; y: number; z?: number }; function printCoordinate(point: PointType) { console.log(point.x, point.y, point.z); }
|
接口声明
可以通过interface
可以用来声明一个对象类型,此处为了和type
声明作对比,简要说明基本用法,详细见接口章节
1 2 3 4 5 6 7 8 9 10 11 12 13
| type PointType = { x: number; y: number; z?: number; };
interface PointType2 { x: number; y: number; z?: number; }
|
类型与接口对比
两者区别
- 类型别名和接口非常相似,在定义对象类型时,大部分时候,可以任意选择使用
- 接口的几乎所有特性都可以在
type
中使用(后续会有interface
的很多特性)
- 如果是定义非对象类型,通常推荐使用
type
,比如Direction
、Alignment
、一些Function
interface
可以重复的对某个接口来定义属性和方法,而type
定义的是别名,别名是不能重复的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| type MyNumber = number; type IDType = number | string;
type PointType1 = { x: number; y: number; };
interface PointType2 { x: number; y: number; }
interface PointType2 { z: number; }
const point: PointType2 = { x: 100, y: 200, z: 300 };
interface IPerson { name: string; age: number; }
interface IKun extends IPerson { kouhao: string; }
const ikun1: IKun = { kouhao: "你干嘛, 哎呦", name: "kobe", age: 30 };
class Person implements IPerson {}
|
交叉类型
另外一种类型合并,就是交叉类型(Intersection Types)
- 交叉类似表示需要满足多个类型的条件
- 交叉类型使用
&
符号
交叉类型栗子:
- 一个交叉类型:表达的含义是
number
和string
要同时满足
- 但是有同时满足是一个
number
又是一个string
的值吗?其实是没有的,所以MyType
其实是一个never
类型
1
| type MyType = number & string;
|
在开发中,进行交叉时,通常是对对象类型进行交叉的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| type NewType = number & string;
interface IKun { name: string; age: number; }
interface ICoder { name: string; coding: () => void; }
type InfoType = IKun & ICoder;
const info: InfoType = { name: "why", age: 18, coding: function () { console.log("coding"); } };
|
非空类型断言
非空断言使用的是 !
,表示可以确定某个标识符是有值的,跳过 ts 在编译阶段对它的检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| interface IPerson { name: string; age: number; friend?: { name: string; }; }
const info: IPerson = { name: "why", age: 18 };
console.log(info.friend?.name);
if (info.friend) { info.friend.name = "kobe"; }
info.friend!.name = "james";
|
字面量类型
1 2 3 4 5 6 7
| const name: "why" = "why"; let age: 18 = 18;
type Direction = "left" | "right" | "up" | "down"; const d1: Direction = "left";
|
字面量推理
栗子中:对象在进行字面量推理的时候,info 其实是一个 {url: string, method: string}
,所以不能将一个 string
赋值给一个 字面量 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| type MethodType = "get" | "post"; function request(url: string, method: MethodType) {}
request("http://www.aaa.com", "post");
const info = { url: "xxxx", method: "post" };
request(info.url, info.method);
request(info.url, info.method as "post");
const info2: { url: string; method: "post" } = { url: "xxxx", method: "post" };
const info3 = { url: "xxxx", method: "post" } as const;
request(info3.url, info3.method);
|
类型缩小
类型缩小的含义
- 类型缩小的英文是 Type Narrowing(也有人翻译成类型收窄)
- 可以通过类似于
typeof padding === "number"
的判断语句,来改变 TypeScript 的执行路径
- 在给定的执行路径中,可以缩小比声明时更小的类型,这个过程称之为 缩小( Narrowing )
- 而编写的
typeof padding === "number"
可以称之为 类型保护(type guards)
常见的类型保护有如下几种:
- typeof
- 平等缩小(比如===、!==)
- instanceof
- in
- 等等…
typeof
在 TypeScript 中,检查返回的值typeof
是一种类型保护,TypeScript 对如何typeof
操作不同的值进行编码
1 2 3 4 5 6 7 8
| function printID(id: number | string) { if (typeof id === "string") { console.log(id.length, id.split(" ")); } else { console.log(id); } }
|
平等缩小
可以使用Switch
或者相等的一些运算符来表达相等性(比如===, !==, ==, and != )
1 2 3 4 5 6 7 8 9 10 11 12 13
| type Direction = "left" | "right" | "up" | "down"; function switchDirection(direction: Direction) { if (direction === "left") { console.log("左:", "角色向左移动"); } else if (direction === "right") { console.log("右:", "角色向右移动"); } else if (direction === "up") { console.log("上:", "角色向上移动"); } else if (direction === "down") { console.log("下:", "角色向下移动"); } }
|
instanceof
JavaScript
有一个运算符instanceof
来检查一个值是否是另一个值的“实例”
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function printDate(date: string | Date) { if (date instanceof Date) { console.log(date.getTime()); } else { console.log(date); }
}
|
in 操作符
Javascript
有一个运算符,用于确定对象是否具有带名称的属性:in运算符
- 如果指定的属性在指定的对象或其原型链中,则
in
运算符返回true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| interface ISwim { swim: () => void; }
interface IRun { run: () => void; }
function move(animal: ISwim | IRun) { if ("swim" in animal) { animal.swim(); } else if ("run" in animal) { animal.run(); } }
const fish: ISwim = { swim: function () {} };
const dog: IRun = { run: function () {} };
move(fish); move(dog);
|