1. 前言
上一篇文章,我们初步认识了TypeScript
,学习了TypeScript
的类型和类型操作。那么这篇文章,我们接着学习TypeScript
的函数部分,包括函数的参数类型、返回值类型等。由于之前我并没有TypeScript
的开发经验,所以把学习的TypeScript
的整个过程记录下来,这将是一个系列。一方面是记录学习的过程,方便随时查阅;另一方面是一点一点分享出来,希望能给想要学习TypeScript
的同学一些帮助。
如果想从0开始学习TypeScript
的同学,极力推荐我之前的写的文章:
第一篇 -- 邂逅TypeScript,搭建简易ts开发环境
学习本文,你将学到:
1. 学会TypeScript的类型断言;
2. 学会可选链的使用;
3. 学会运算符`!!`和`??`的使用;
4. 学会字面量类型;
5. 学会函数的几种类型;
6. 等等
2. 类型断言
2.1 类型断言(Type Assertions
)
类型断言就是把比较宽泛的类型转换成一个具体的类型,语法就是使用as
关键字;只看概念的话,不容易理解,我们看下具体的使用场景:
场景一:
比如我们获取一个DOM节点,通常会这么写:
const el = document.querySelector('#id');
这个时候我们把鼠标放在el
上(文章中所有的demo都是使用vscode
编辑器);会看到el
的类型是Element
,也就是说是个元素类型,有可能是div
元素,也有可能是image
元素,有可能是任何一种元素;这时候如果想给el
设置src
属性,编辑器会提示错误:类型“Element”上不存在属性“src”;
// 报错:类型“Element”上不存在属性“src”
el.src = './1.png'
为什么会报错呢?因为这个时候,我们并不能确定el
这个元素是image
元素,那么如何解决呢?这个时候就会用到类型断言了。
const el = document.querySelector('#id') as HTMLImageElement;
// 这个时候`el`是`HTMLImageElement`类型,
// `HTMLImageElement`类型是有`src`属性的,所以下边的代码会编译通过
el.src = './1.png' // ok
可以看到,通过类型断言,把Element
比较宽泛的类型,转成了HTMLImageElement
具体类型;
场景二:
在类中使用类型断言:
class Person {};
// Student 继承 Person 类
class Student extends Person {
study() {};
}
// sayHello函数接受一个必选参数,类型是`Person`
function sayHello(p: Person) {
// p.study(); // Error 如果这么写,报错:类型“Person”上不存在属性“study”
// 所以这里可以使用类型断言来解决这个问题,
// 把比较宽泛的 Person 类型转成更具体的 Student 类型;
(p as Student).study(); // ok
}
const s = new Student();
sayHello(s);
在sayHello
函数中,直接调用p.study()
会报错:类型“Person”上不存在属性“study”,因为Person
类中确实没study
这个方法,但是把Person
类型转换为 它的子类Student
就可以了。
2.2 非空类型断言
在上下文中,当类型检查器无法断定类型时,一个新的后缀表达式操作符 !
可以用于断言操作对象是非 null
和非 undefined
类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。
使用场景:
function foo(message: string | null | undefined) {
// 编译Error: Type 'string | null | undefined' is not assignable to type 'string'
const msg: string = message;
// 编译ok
const ignoreNullAndUndefined: string = message!;
}
3. 可选链操作符 ?. 的使用
可选链操作符?.
其实不是TypeScript
独有的特性,它是ES11中新增的特性:
- 可选链操作符
?.
; - 它的作用是,当对象的属性不存在时,会短路,直接返回undefined, 如果存在才会继续执行;
- 虽然是ECMA提出的,但是可以和
TypeScript
一起使用;
看下使用场景:
// 定义类型别名 Person
type Person = {
name: string,
friend?: {
name: string,
car?: {
color?: string
}
}
}
// 声明 info 对象
const info: Person = {
name: 'hsh',
friend: {
name: 'jack'
}
}
// Error:Object(info.friend) is possibly 'undefined'.
// 之所以会编译报错,是因为`friend`是一个可选类型,可选类型的话就有可能是`undefined`
const friendName = info.friend.name
// 编译ok,`friend`后加了可选链操作符
const friendNameNew = info.friend?.name
// 编译ok, 因为声明的`info`对象的`friend`对象里没有`car`属性
// 所以到`car`这就返回了`undefined`
const color = info.friend?.car?.color // 输出:undefined
4. 运算符 ?? 和 !! 的使用
4.1 ??
的使用
??
是ES11(ECMAScript2020)新增的操作符,称为空值合并运算符。
使用场景:
const msg: string | null = null;
// 如果`msg`存在,取`msg`的值,不存在取`??`后边的值
const content = msg ?? '这是默认值';
console.log(content) // 输出:‘这是默认值’
// 效果等价于 const content = msg || '这是默认值';
4.2 !!
的使用
!!
操作符的作用就是把其他类型转换为 Boolean 类型。
使用场景:
const message: string = 'hello';
const flag = !! message; // 输出:true
// 等价于 Boolean(message)
5. 字面量类型
字面量类型一般结合联合类型使用:
let align: 'left' | 'right' | 'center'
align = 'left' // ok
align = 'right' // ok
align = 'center' // ok
// 不能将类型“"top"”分配给类型“"left" | "right" | "center"”
align = 'top' // error
// 不能将类型“"down"”分配给类型“"left" | "right" | "center"”
align = 'down' // error
可以看出来,能直接将字面量当做是类型,一般配合联合类型使用,可以很好的限制变量的取值范围。
6. 类型守卫
类型守卫其实就是缩小类型范围的,在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为缩小。
常用的类型守卫一般有这四种:typeof
、instanceof
、in
操作符和平等缩小(比如:===
、!==
),分别举例说明一下:
6.1 typeof
function foo(value: string | number) {
if(typeof value === 'string') {
value.toUpperCase();
// value++; // Error: 因为`value`这里只能是`string`类型
} else {
value++;
// value.toUpperCase(); // Error: 因为`value`这里只能是`number`类型
}
}
typeof
类型保护只支持两种形式:typeof v === "typename"
和 typeof v !== typename
。
6.2 instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
class Teacher {
teaching() {};
}
class Student {
studying() {};
}
function todo(p: Teacher | Student) {
if(p instanceof Teacher) {
p.teaching();
} else {
p.studying();
}
}
6.3 in
如果指定的属性在指定的对象或其原型链中,则in
运算符返回true
。
type Teacher = {
name: string,
teach: boolean
}
type Student = {
name: string,
study: boolean
}
function printInfo(p: Teacher | Student) {
console.log(p.name);
if('teach' in p) {
console.log('teach:', p.teach);
}
if('study' in p) {
console.log('study:', p.study);
}
}
6.4 ===
和!==
type Direction = 'left' | 'right' | 'top' | 'bottom';
function printDirection(direction: Direction) {
if(direction === 'left') {
// todo something
}
}
7. 函数的几种类型
7.1 函数作为参数时的类型
type FooFnType = () => void; // 无参数无返回值函数类型
function bar(fn: FooFnType) {
fn();
}
function foo() {}
bar(foo); // ok
7.2 定义常量时,编写函数的类型
type AddFnType = (a: number, b: number) => number;
const add: AddFnType = (num1: number, num2: number) => {
return num1 + num2;
}
console.log(add(10, 20)); // ok
7.3 函数参数的可选类型
注意:可选参数必须写在必选参数后边。
function optional(num1: number, num2?: number) {
console.log(num1)
}
optional(20, 30); // ok
optional(20); // ok
optional(20, undefined); // ok
optional(); // Error: 应有 1-2 个参数,但获得 0 个。
7.4 函数参数的默认值
// 如果参数有默认值,相当于是个联合类型;
// 比如下边的例子:`num2`的类型其实等价于 number | undefined
function defaultValue(num1: number, num2: number = 30) {}
defaultValue(20); // ok
defaultValue(20, 20); // ok
defaultValue(20, undefined); // ok
defaultValue(); // Error: 应有 1-2 个参数,但获得 0 个。
7.5 剩余参数 ...args
function rest(num1: number, ...args: number[]) {
let total = num1;
for (const i of args) {
total += i;
}
return total;
}
// rest 可以接受任意数量的参数,但至少是一个
const result1 = rest(10);
console.log(result1);
const result2 = rest(10, 20, 30);
console.log(result2);
8 . 总结
这篇文章主要学习了类型断言、类型守卫、扩展操作符和函数的几种类型,其实非常建议大家去手动的敲一遍文章中的例子,如果只是看一遍的话,很快就会忘掉的。
持续更新中...