上期学习了如何安装运行 TS 项目以及 TS 的基础类型,对象类型 这期学习 TS 剩余的类型学习完成, 希望能保持周更吧~
TypeScript 中的类型注释和类型推断
TypeScript 中的两个基本概念:类型注解和类型推断,这两个概念在我们编写 TypeScript 代码时会一直使用(重点)
type annotation 类型注解
let count: number;
count = 123;
这段代码就是类型注解,意思是显示的告诉代码,我们的count 变量就是一个数字类型,这就叫做类型注解
type inferrence 类型推断
类型推断:指编程语言中能够自动推导出值的类型的能力。
它是一些强静态类型语言中出现的特性;定义时未赋值就会推论成 any 类型 如果定义的时候就赋值就能利用到类型推论
let flag; //推断为any
let count = 123; //为number类型
let hello = "hello"; //为string类型
联合类型和类型保护
只有联合类型存在的情况下,才需要类型保护,普通的类型注解,并不需要我们这种特殊操作。那先来看一下什么是联合类型
联合类型(Union Types)
所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。举个🌰
interface Waiter {
anjiao: boolean;
say: () => {};
}
interface Teacher {
anjiao: boolean;
skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {
animal.say();
}
这样写又个问题,直接写一个这样的方法,就会报错,因为judgeWho不能准确的判断联合类型具体的实例是什么。
这时候就需要再引出一个概念叫做类型保护,类型保护有很多种方法,这里统计下最常用的。
类型保护-类型断言
类型保护就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型 其主要思想是尝试检测属性、方法或原型,以确定如何处理值
类型断言就是通过断言的方式确定传递过来的准确值,比如上面的程序,如果会anjiao(按脚),说明他就是技师,这时候就可以通过断言animal as Teacher,然后直接调用skill方法,程序就不再报错了。同样如果不会按脚,说明就是不同的服务员,这时候调用say()方法,就不会报错了。这就是通过断言的方式进行类型保护。也是最常见的一种类型保护形式。具体看代码:
interface Waiter {
anjiao: boolean;
say: () => {};
}
interface Teacher {
anjiao: boolean;
skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {
if (animal.anjiao) {
(animal as Teacher).skill();
}else{
(animal as Waiter).say();
}
}
类型断言有两种形式:
// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
PS: 以上两种方式虽然没有任何区别,但是尖括号格式会与 react 中 JSX 产生语法冲突,因此我们更推荐使用 as 语法。
非空断言 在上下文中当类型检查器无法断定类型时 一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型
let flag: null | undefined | string;
flag!.toString(); // ok
flag.toString(); // error
双重断言
类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作:
如下例子中的代码将会报错,尽管使用者已经使用了类型断言:
function handler(event: Event) {
const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}
如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:
function handler(event: Event) {
const element = (event as any) as HTMLElement; // ok
}
TypeScript 是怎么确定单个断言是否足够
当 S 类型是 T 类型的子集,或者 T 类型是 S 类型的子集时,S 能被成功断言成 T。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以使用 any
类型保护-in 语法
使用in语法来作类型保护,比如用if来判断animal里有没有skill()方法。
复制上面的 judgeWho()方法,改一下名称:
function judgeWhoTwo(animal: Waiter | Teacher) {
if ("skill" in animal) {
animal.skill();
} else {
animal.say();
}
}
再举一个🌰
interface Bird {
fly: number;
}
interface Dog {
leg: number;
}
function getNumber(value: Bird | Dog) {
if ("fly" in value) {
return value.fly;
}
return value.leg;
}
console.log(getNumber({fly: 123}));
这里的else部分能够自动判断,得益于TypeScript的自动判断。
类型保护-typeof 语法
写一个新的add方法,方法接收两个参数,这两个参数可以是数字number也可以是字符串string,如果我们不做任何的类型保护,只是相加,这时候就会报错。代码如下:
function add(first: string | number, second: string | number) {
return first + second;
}
解决这个问题,就可以直接使用typeof来进行解决。
function add(first: string | number, second: string | number) {
if (typeof first === "string" || typeof second === "string") {
return `${first}${second}`;
}
return first + second;
}
再举一个🌰
function double(input: string | number | boolean) {
if (typeof input === "string") {
return input + input;
} else {
if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}
}
类型保护-instanceof 语法
如果要作类型保护的是一个对象,这时候就可以使用instanceof语法来作。现在先写一个NumberObj的类,代码如下:
class NumberObj {
count: number;
}
然后我们再写一个addObj的方法,这时候传递过来的参数,可以是任意的object,也可以是NumberObj的实例,然后我们返回相加值,这里不进行类型保护,那这段代码会报错,如下:
function addObj(first: object | NumberObj, second: object | NumberObj) {
return first.count + second.count;
}
使用instanceof语法进行判断一下
function addObj(first: object | NumberObj, second: object | NumberObj) {
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count;
}
return 0;
}
instanceof 只能用在类上
字面量类型
在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型
目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型,具体示例如下:
let flag1: "hello" = "hello";
let flag2: 1 = 1;
let flag3: true = true;
类型别名
类型别名用来给一个类型起个新名字
type flag = string | number;
function hello(value: flag) {}
TypeScript 提供了为类型注解设置别名的便捷语法,你可以使用 type SomeName = someValidTypeAnnotation 来创建别名:
type StrOrNum = string | number;
// 使用
let sample: StrOrNum;
sample = 123;
sample = '123';
// 会检查类型
sample = true; // Error
与接口不同,你可以为任意的类型注解提供类型别名(在联合类型和交叉类型中比较实用),下面是一些能让你熟悉类型别名语法的示例。
type Text = string | { text: string };
type Coordinates = [number, number];
type Callback = (data: string) => void;
TIP
- 如果你需要使用类型注解的层次结构,请使用接口。它能使用 `implements` 和 `extends`
- 为一个简单的对象类型(如上面例子中的 Coordinates)使用类型别名,只需要给它一个语义化的名字即可。另外,当你想给联合类型和交叉类型提供一个语义化的名称时,一个类型别名将会是一个好的选择。
交叉类型
在 JavaScript 中, extend 是一种非常常见的模式,在这种模式中,你可以从两个对象中创建一个新对象,新对象拥有着两个对象所有的功能。交叉类型可以让你安全的使用此种模式:
function extend<T extends object, U extends object>(first: T, second: U): T & U {
const result = <T & U>{};
for (let id in first) {
(<T>result)[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<U>result)[id] = second[id];
}
}
return result;
}
const x = extend({ a: 'hello' }, { b: 42 });
// 现在 x 拥有了 a 属性与 b 属性
const a = x.a;
const b = x.b;
交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
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)
小结
TypeScript 的类型系统学到这里~
参考文章
最全的TypeScript学习指南
技术胖的 TypeScript免费视频图文教程(2W字)
TS的对象类型、数组类型、函数类型
掌握 tsconfig.json
深入理解 TypeScript
1.2W字 | 了不起的 TypeScript 入门教程
TS 推荐学习网站
TypeScript 中文手册
技术胖 - TypeScript 从入门到精通图文视频教程-免费教程
深入理解 TypeScript
TypeScript 官方提供的在线 TypeScript 运行环境
如果大佬还知道更多的学习网站,欢迎留言~