前言
本周的主要任务是过完 TypeScript 的基础知识。因为 TypeScript 不是我想要分享、学习的重点,我们仅仅是为了利用 TypeScript 来养成一个良好的编码习惯,所以过的会快一些。
另外, TypeScript 的基础知识网上有非常多的教程,想要深入学习的同学不妨找来看看。 在这里我推荐下这个系列的教程:TypeScript 入门教程。
学习目标
- 简单的类型声明
类型声明
原始数据类型
原始数据类型有 7种 ,分别是:
- boolean
- null
- undefined
- number
- string
- symbol
- bigint
忽略掉 ES6 中新增的 symbol 与 bigint 类型,其他的类型声明方法大致如下:
const name: string = '张三';
const age: number = 18;
const isMarried: boolean = false;
const u: undefined = undefined;
const n: null = null;
值得注意的是,使用 Boolean 、 String 、 Number 这类构造函数创建的对象,不能使用原始数据类型:
const b: boolean = new Boolean(false); // 报错
const b: Boolean = new Boolean(false); // 正确
但是最好不要将包装对象用作于构造函数。
任意值
let anything: any = '18';
anything = 18;
被声明为任意值的变量在被赋值时可以被更改为任意类型的值,但是能不用 any 就不用 any ,否则类型检查的意义就不是很大了。
接口
TypeScript 对象的接口可以用于对类的一部分行为进行抽象,也可以用于对对象的结构的描述。
简单应用
一个简单的例子是:
interface Person {
name: string;
age: number;
}
const person: Person = {
name: 'tom',
age: 18,
};
赋值的时候,变量的形状必须和接口的形状保持一致。属性不能多,也不能少。
可选属性
如果希望属性可选,那么我们可以这么定义:
interface Person {
name: string;
age: number;
sayHello?: Function;
}
const person: Person = {
name: 'tom',
age: 18,
};
如此一来,sayHello 就是一个可选属性了。但是仍然不能添加未定义的属性。
任意属性
当需要定一个一个未在接口中声明的属性时,我们可以使用这种形式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
const person: Person = {
name: 'tom',
age: 18,
gender: 'male',
};
其中需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
数组
数组拥有多种表示方法,非常灵活。
类型[方括号] 表示法
const fibonacci: number[] = [1, 1, 2, 3, 5];
数组范型
数组范型Array<elemType>也可以用来表示数组:
const fibonacci: Array<number> = [1, 1, 2, 3, 5];
函数
首先来复习下基础知识,函数有两种常见的定义方式。函数声明和函数表达式。
函数声明:
function sum(x, y) {
return x + y;
}
函数表达式:
const sum = function(x, y) {
return x + y;
};
通用表示
用 TypeScript 对函数进行约束,肯定是既约束参数,又约束返回值。
注意:输入多余的(或者少于要求的)参数,是不被允许的
函数声明:
function sum(x: number, y: number): number {
return x + y;
}
函数表达式:
const sum: (x: number, y: number) => number = (x: number, y: number): number => {
return x + y;
};
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
上面的第一个 => 是 TypeScript 类型定义中的 => ,而第二个 => 则是 ES6 中的箭头函数。
可选参数
和接口类似,我们使用?来表示可选参数:
function sum(x: number, y: number, z?: number) {
if (z) {
return x + y + z;
}
return x + y;
}
注意:可选参数必须接在必需参数后面
参数默认值
TypeScript 会将添加了默认值的参数识别为可选参数,并且此时不受「可选参数必须接在必需参数后面」的限制了:
function sum(x: number = 0, y: number) {
return x + y;
}
sum(undefined, 3); // 3
剩余参数
剩余参数实际上是一个数组,所以我们可以用数组的类型来定义它:
function push(arr: any[], ...items: any[]) {
items.forEach((item) => {
arr.push(item);
});
}
push([], 1, 2, 3); // [1, 2, 3]
注意:rest 参数只能是最后一个参数
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
function reverse(x: number | string): number | string | undefined {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
return undefined;
}
这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reverse 的函数类型:
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | undefined {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
return undefined;
}
上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
类型断言可以手动的指定一个值的类型。
之前说过, TypeScript 不确定联合类型变量到底是哪个类型的时候,我们只能访问此联合类型中的所有类型共有的方法和属性:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function getName(animal: Cat | Fish) {
return animal.name;
}
而当我们访问某个类型特有的属性或方法时,就会报错:
function isFish(animal: Cat | Fish) {
if (typeof animal.swim === 'function') {
return true;
}
return false;
}
此时,我们可以将 animal 断言为 Fish:
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
类型断言还有很多其他的使用场景,但是核心就是:只有当你确定、确定、确定某个值的类型时,再用断言。
结语
好了,本期的内容差不多就到这里了,如果是有其他静态语言经验的同学,学起来应该会比较快。还是我最开始说的那句话,我们学习 TypeScript 的目的是为了养成良好的编码习惯,而养成良好编码习惯的目的是写出更健壮的代码。本来是想花一周时间来过完 TypeScript 的简单使用了,但是目前来看,有的内容得放到下周了。就酱,溜了溜了。(PS:这周真的太忙了,希望下周不摸)