前言
TypeScript
是
JavaScript
的超集,为
JavaScript
赋予了强类型语言的特性。在项目中使用
TypeScript
能够将错误提前暴露在开发阶段,并且
TypeScript
除保留
JavaScript
原有特性外还增加了很多强大的特性,如:
接口
、
泛型
、
类
等等,为开发带来了极大的便利。前阵子在学习
TypeScript
过程中整理了不少笔记,在梳理过程中就产生了该文章,同时也希望让更多同学了解
TypeScript
,顺便帮自己加深记忆。
TypeScrip中的数据类型
TypeScript
的数据类型,包含了
JavaScript
所有的数据类型。除此之外还提供了
枚举
、
元组
、
any
、
void
等类型,下面讲述在
TypeScript
中如何使用这些类型。
JavaScript中原有的数据类型
- Boolean
// Boolean
let bool: boolean = true;
复制代码
- String
// String
let str: string = 'hello TypeScript';
复制代码
-
Number
- JavaScript一样,TypeScript里的所有数字都是浮点数
-
除支持十进制、十六进制外,
TypeScript
还支持二进制、八进制
// Number
let decLiteral: number = 10;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
复制代码
-
Array
-
Array分两种声明方式:
- 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
- 第二种方式是使用数组泛型,Array<元素类型>
-
Array分两种声明方式:
// 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
let arr1: number[] = [1, 2, 3];
// 第二种方式是使用数组泛型,Array<元素类型>
let arr2: Array<number> = [1, 2, 3];
// 数组支持多种元素类型
let arr3: (number | string) = [1, '2', 3];
let arr4: Array<number | string> = [1, '2', 3];
复制代码
- Function
// Function
let add = (x, y) => x + y;
复制代码
- Object
// 可以这样定义,但访问、修改属性时会抛出错误,因为Object上不存在x、y属性。
let obj: Object = {x: 1, y: 2};
console.log(obj.x); // 类型“Object”上不存在属性“x”
// 完善定义
let obj1: {x: number, y: number} = {x: 1, y: 2};
console.log(obj1.x); // 1
复制代码
- Symbol
// Symbol
let syb: symbol = Symbol('hello');
复制代码
- null与undefined
// null与undefined
let null: null = null;
let undefined: undefined = undefined;
复制代码
上面示例中我们在变量后跟类型的方式,被称之为:
类型注解
。使用
类型注解
的变量必须按照类型注解声明的类型来赋值,否则
TypeScript
会抛出错误。
TypeScript新增的数据类型
-
void
-
void
代表没有返回值,或返回值为undefined
,通常用来声明函数的返回值类型。 -
如果一个变量被申明为
void
类型,那它只能被赋值为null
与undefeated
。
-
// void
let add = (x: number, y: number): void => undefined; // 表示返回值为undefined
let unusable: void = undefined;
unusable = 'hello'; // ts会抛出错误,不能将类型“"hello"”分配给类型“void”
复制代码
-
any
-
any
可以被赋值为除never
外的任何类型。 -
注:可以使用
any
,但是不建议全部使用any
来定义类型,这样使用TypeScript
就没有任何意义了。
-
// any
let any: any = 1;
any = '1';
any = null;
复制代码
-
never
-
never
类型表示的是那些永不存在的值的类型 -
返回
never
的函数必须存在无法达到的终点,比如:一个死循环的函数、一个总是抛出错的函数。 -
never
只能被赋值为never
自身。
-
// never
function loop(): never {
while(true) {}
}
function error(message: string): never {
throw new Error(message);
}
复制代码
-
元组
- 元组类型表示一个已知元素数量和类型的数组,各元素的类型不必相同
- 元组越界会抛出错误
// 元祖 tuple
let tuple: [number, string] = [1, '2'];
tuple[2] = 2; // 不能将类型“2”分配给类型“undefined”。
复制代码
-
枚举
- 枚举分为两种类型: 数字枚举 与 字符串枚举
- 枚举成员的值为只读类型,不能修改
- 默认为数字枚举,从0开始,依次递增
-
枚举成员类型分为两类:
常量枚举
与
计算型枚举
-
常量枚举:常量枚举会在编译时计算出结果,已常量的方式保存。常量枚举赋值分以下三种情况:
- 没有初始值
- 对已有成员的引用
- 常量表达式
-
计算型枚举:需要被计算的枚举成员,计算型枚举不会在编译时计算,会保留到执行阶段。
- 计算型枚举后的枚举成员必须要赋值
-
常量枚举:常量枚举会在编译时计算出结果,已常量的方式保存。常量枚举赋值分以下三种情况:
- 需要注意的是,包含字符串的枚举中不能使用计算型枚举
- 注: 通常枚举会用于权限、状态判断。避免"魔术数字"的出现,提高可读性、可维护性。
// 数字枚举,默认从0开始,依次递增
enum Color {
Red,
Green,
Blue
}
console.log(Color.Red) // 0
console.log(Color.Red) // 1
console.log(Color.Red) // 2
// 自定义数字枚举的值
enum Alphabet {
A = 8,
B,
C = 2020
}
console.log(Alphabet.A) // 8
console.log(Alphabet.B) // 9
console.log(Alphabet.C) // 2020
// 类型保护
let year = Alphabet.C;
year = 'today'; // 不能将类型“"today"”分配给类型“Alphabet”
// 数字枚举支持双向映射
console.log(Alphabet.A) // 8
console.log(Alphabet[8]) // A
// js实现双向映射
var Alphabet;
(function (Alphabet) {
Alphabet[Alphabet["A"] = 8] = "A";
Alphabet[Alphabet["B"] = 9] = "B";
Alphabet[Alphabet["C"] = 2020] = "C";
})(Alphabet || (Alphabet = {}));
// 字符串枚举
enum Message {
Fail = '失败',
Success = '成功'
}
// 枚举成员类型
enum Library {
// 常量枚举
BookName,
Year = 2020,
Count = BookName + Year,
// 计算型枚举
Number = Math.random(),
Size = '如何成为掘金V6'.length // 计算型枚举后的枚举成员必须要赋值
}
复制代码
看完上面的内容,我们已经对
TypeScript
中的数据类型已经有一个大概的了解了。有同学可能会有疑问,如果我们需要描述一个复杂的数据类型该怎么编写类型注解呢?这就涉及到我们下面讲的内容
接口
,如何使用
接口
来描述一个复杂的数据类型。
接口
接口
是
TypeScript
中核心概念之一,
主要用于约束对象、函数、类的结构
。
-
如何声明一个接口?
- 可使用 interface 关键字声明接口
-
接口可以限制哪些属性?
-
可选属性:
x?: string
,来约定参数是否可选 -
只读属性:
readonly x: number
,来约定参数是否只读
-
可选属性:
-
接口分几种类型?
- 对象类型
-
函数类型
- 函数的参数名不需要与接口里定义的名字相匹配,但要求对应位置上的类型是兼容的
- 索引类型
- 混合类型
-
注:接口具有
鸭式辩型法
特性。
-
什么是
鸭式辩型法
?
- 答: 一只鸟如果走起来像鸭子,叫起来像鸭子,游起来像鸭子,那么这只鸟就能被当成一只鸭子 。这就是 鸭式辨型法 ,下面我们会举例说明。
-
什么是
鸭式辩型法
?
接口的几种类型
/**
* 对象类型接口
*/
// 可选属性与只读属性
interface Person {
name: string;
readonly age: number;
sex: 'boy' | 'girl',
hobby?: string[]
}
let Tom: Person = {
name: 'Tom',
age: 3,
sex: 'boy'
};
Tom.age = 1; // Cannot assign to 'age' because it is a read-only property.
let Jerry: Person = {
name: 'Jerry',
age: 3,
sex: 'boy',
hobby: ['和汤姆一起快乐的玩耍']
}
/**
* 鸭式辩型法举例
* renderList传入的参数只要满足Result接口的约定即可,多余的code、msg不会影响
* 这也是我们提到的:一只鸟如果走起来像鸭子,叫起来像鸭子,游起来像鸭子,那么这只鸟就能被当成一只鸭子。
*/
interface ListItem {
id: number;
name: string
}
interface Result {
list: ListItem[]
}
function renderList(result: Result) {
result.list.forEach((item: ListItem) => {
console.log(item.id, item.name)
})
}
const data = {
list: [{
id: 1,
name: '小红'
}, {
id: 2,
name: '小白'
}],
code: 1,
msg: ''
};
renderList(data);
/**
* 函数类型接口
* 函数的参数名不需要与接口里定义的名字相匹配,但要求对应位置上的类型是兼容的
*/
let add: (x: number, y: number) => number;
interface Add {
(x: number, y: number): number;
}
// 上面这两种定义式等价的
// 函数的参数名不需要与接口里定义的名字相匹配
let add1: Add = (j: number, k: number) => j + k;
// 但要求对应位置上的类型是兼容的
let add2: Add = (j: string, k: number) => j + k; // 不能将类型“(j: string, k: number) => string”分配给类型“Add”。参数“j”和“x” 的类型不兼容。
/**
* 索引类型接口
* 索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
* 当不确定接口中属性个数时可以使用索引类型接口
* 索引类型分字符串索引和数字索引
* 字符串与数字索引可以同时使用,但数字索引必须是字符串索引返回的子类型
*/
// 数字索引
interface StringArray {
[index: number]: string;
}
const myArra1: StringArray = ['1', 2]; // 不能将类型“number”分配给类型“string”。
const myArray: StringArray = ['1', '2'];
// 字符串索引
interface ListItem {
id: number;
name: string;
[x: string]: any;
}
// 这里我们使用string去索引any
let list: ListItem[] = [{
id: 1,
name: 'aa',
year: '2019'
}]
// 字符串与数字索引可以同时使用,但数字索引必须是字符串索引返回的子类型
interface ArrayItem {
[x: string]: string;
[y: number]: number; // 数字索引类型“number”不能赋给字符串索引类型“string”
}
interface ArrayItem1 {
[x: string]: string;
[y: number]: string;
}
/**
* 混合类型接口
* 混合类型接口是函数类型接口与对象类型接口的集合
*/
interface Lib {
(): void;
version: string;
doSomething(): void;
}
let lib: Lib = (() => {}) as Lib;
lib.version = '1.0.0';
lib.doSomething = () => {};
复制代码
类型别名与类型断言
-
什么是类型别名?
-
通常我们会使用
interface
声明接口,但
类型别名
也能达到同样的作用,甚至更方便简洁。
-
如何声明类型别名?
- 类型别名 使用 type 关键字声明。
-
如何声明类型别名?
-
通常我们会使用
interface
声明接口,但
类型别名
也能达到同样的作用,甚至更方便简洁。
-
什么是类型断言?
-
有时候你会比
TypeScript
更清楚数据的类型,这时候就能使用 类型断言 ,表示你清楚该数据的类型格式。-
如何使用
类型断言
?
-
与JSX一致,使用
<类型>
的方式来声明类型断言。 -
使用
as
进行类型断言。 - 注: 第一种方式在React中会与JSX冲突,推荐统一使用第二种方式。
-
与JSX一致,使用
-
如何使用
类型断言
?
-
有时候你会比
// 使用接口声明一个函数类型接口
interface Add {
(x: number, y: number): number
}
// 使用类型别名声明一个函数类型接口
type Add = (x: number, y: number) => number;
// 上面两种声明方式等价
/**
* 类型别名的一些小技巧
* 在React中初始化state,设置类型比较麻烦
* 如果使用类型别名就很方便了
*/
const initialState = {
page: 1,
pageCount: 15,
list: [{
id: 1,
name: 'Tom'
}]
};
type State = typeof initialState;
type State1 = {
page: number;
pageCount: number;
list: {
id: number;
name: string;
}[];
}
// State与State1两者等价
/**
* 类型断言
* 类型断言有两种声明方式:1. 与JSX一般声明,2. 使用as关键字声明
* 在React中请使用第二种方式,同时也推荐使用第二种方式
*/
interface Obj {
x: number;
y: number;
}
let obj = {};
obj.x = 1; // 类型“{}”上不存在属性“x”
// 1. 如JSX一般声明
let obj1 = <Obj>{};
obj1.x = 1;
// 2. 使用as关键字声明
let obj2 = {} as Obj;
obj2.x = 1;
复制代码
函数
TypeScript
中的函数行为基本与
JavaScript
保持一致,不同的是
TypeScript
支持以下功能:
-
支持约束参数类型、数量
- 函数必须按照约定参数类型与数量传入参数,否则会抛出错误。
-
支持定义函数返回值的类型
- 表示函数是否存在返回值、以及返回值的类型
-
支持可选参数
- 可选参数必须位于必选参数之后
-
支持函数重载
-
TypeScript
规定必须在类型最宽泛的版本中实现重载,你可以理解为在类型最为宽泛的版本中需要支持之前版本的所有类型。
-
// 约束参数类型、数量