typescript 学习之基础篇
2.6k10WEBtypescript2019-03-25

vue-cli已经内置了 TypeScript 工具支持,在 Vue (3.x) 中也计划了相当多的 TypeScript 支持改进,很多大佬以及用过的人也都在推荐入坑,而且目前也比较成熟,资源也很丰富,值得入坑

? typescript

官方介绍 TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上
我的理解是 TypeScript扩展了JavaScript的语法,为 js 增加了类型机制,引入类,接口,枚举,泛型,方法重载等一系列的概念和方法,丰富了js的使用,而且和 VScode 搭配写起来是真的舒服

全局安装:npm install -g typescript
编译命令:tsc hello.ts

TypeScript 编写的文件以 .ts 为后缀,用 TypeScript 编写 React 时,以 .tsx 为后缀
在报错的时候终止 js 文件的生成,在 tsconfig.json 中配置 noEmitOnError

忽略下一行代码

1
2
// @ts-ignore
const scrollbar = () => import('./ScrollBar/index.ts')

原始数据类型声明

原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。
在ts中变量声明,变量或者参数都必须要声明其类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolean
let isDone: boolean = false;
let createdByBoolean: boolean = Boolean(1);

number
let num: number = 6; // 6

string
let string = 'string'
// ` 用来定义 ES6 中的模板字符串,${expr} 用来在模板字符串中嵌入表达式
let sentence: string = `Hello, my name is ${myName}.I'll be ${myAge + 1} years old next month.`;

Null 和 Undefined 和 void
let u: undefined = undefined;
let n: null = null;
// 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
let unusable: void = undefined; // void只能赋值为 undefined 和 null

内置对象:指根据标准在全局作用域(Global)上存在的对象,这些对象可作为类型赋值给变量

这些内置对象的定义文件可以在TypeScript 核心库的定义文件查找

ECMAScript 标准的内置对象比如Boolean、Date、RegExp等,更多内置对象MDN

1
2
3
let b: Boolean = new Boolean(1);
let d: Date = new Date();
let r: RegExp = /[a-z]/;

DOM 和 BOM 的内置对象比如Document、HTMLElement、Event、NodeList等

1
2
3
4
5
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// ...
});

赋值多个类型

任意类型 any :表示允许赋值为任意类型,且能在赋值过程中改变类型

1
2
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

变量声明,赋值但未指定其类型,TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型

1
2
let a = '7' // TypeScript会推断为string类型
a = 7 // 这个赋值会导致报错

变量声明,未赋值未指定其类型,会被推断成 any 类型而完全不被类型检查

1
2
3
let a    // any 类型
a = '7'
a = 7

联合类型:可以指定多种类型,可赋值为其中一种,使用 |` 分隔每个类型

示例

1
2
3
4
let a: string | number; // 指定类型为 string 和 number
a = '7'; // 类型推断为 number
a = 7; // 类型推断为 string
a = true; // 类型推断为 boolean 报错

使用接口定义对象类型

  • 接口使用 interface 关键字定义

  • 接口首字母大写

  • 对象类型的属性分为确定属性、只读属性、可选属性、任意属性

  • 确定属性:也就是固定的属性,接口有 变量声明时就必须有,

  • 只读属性:readonly 关键字定义,第一次赋值后无法更改,是在接口创建后只能** 第一次给对象赋值的时候为只读属性赋值 **, 而不是第一次给只读属性赋值

  • 可选属性:属性名 + ?,表示该属性可以不存在

  • 任意属性:[propName: string]: any; 冒号前半部分表示任意属性取 string类型的值,后半部分是类型的子集

  • 一旦定义了任意属性,那么另外三种属性的类型都必须是它的类型的子集,可将任意属性的类型的子集定义为 联合类型 或者 any 类型

一个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Person {
readonly id: number; // 只读属性
a: string; // 确定属性
b?: number; // 可选属性
[propName: string]: string; // 任意属性
} // 会报错,任意属性类型的子集为 string,而 b 和 id 的类型为number,不属于任意属性类型的子集

let obj: Person = { // 报错 没有给只读属性 id 赋值
a: '123',
b: 456,
c: '789',
};
obj.id = 89757; // 报错 因为这不是对象第一次赋值

修改为

1
2
3
4
5
6
7
8
9
10
11
12
interface Person {
readonly id: number;
a: string;
b?: number;
[propName: string]: string | number;
}
let obj: Person = {
id: 8,
a: '123',
b: 456,
c: '789',
};

数组类型:类型 + 方括号, ** 数组的每一项元素都必须符合类型 **

1
2
3
let a: number[] = [1, 2, 3, 5]; // 数组元素类型只能是 number
let c: (number | string)[] = [1, '1', 2, 3, 5]; // 联合类型和数组的结合
let b: any[] = [1, '2', 3, {a: 1}]; // any 类型

接口定义

1
2
3
4
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

函数的类型定义

  • 函数的输入和输出为必须,且都必须定义类型
  • 可选参数:参数名 + ? ,可选参数必须在必填参数后面
  • 默认参数:设置了默认值的参数会被 TypeScript 识别为可选参数,但不受 “可选参数必须在必填参数后面” 这个限制
  • 剩余参数:指除了 必选参数 和 可选参数 以外的参数,使用 … 获取剩余参数为数组格式需定义类型,剩余参数必须排在参数末尾
1
2
3
4
5
6
7
8
9
10
11
// 小括号里面是输入的参数包括对参数类型的定义,花括号前面的number是输出的类型
// y 设置了默认参数,为可选参数
// z为可选参数
// rest 为剩余参数,any 类型
function sum(x: number, y: number = 6, z?: number, ...rest: any[]): number {
return x + y;
}
sum(1)
sum(1, 2)
sum(1, 2, 3)
sum(1, 2, 3, 4, 5) // 4,5 为剩余参数

函数表达式: =>不是箭头函数,他表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型

1
2
3
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};

类型断言:<类型>值,当需要在还不确定类型的时候就访问其中一个类型的属性或方法时我们可以手动指定这个值的类型

1
2
3
4
5
6
7
8
9
function getLength(something: number): number { // 当然这地方你也可以将他定义为any类型
return something.length; // 报错 类型“number”上不存在属性“length”
}
```
使用类型断言
````ts
function getLength(something: string): number {
return (<string>something).length; // 将参数定义为string后再去访问他的length
}

类型别名:使用type关键字给一个类型起个新名字,常用于联合类型

1
2
3
4
5
6
7
8
9
10
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}

字符串字面量类型:使用type关键字约束取值只能是某几个字符串中的一个

1
2
3
4
5
6
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll');
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,dbclick不在EventNames中

声明文件

  • 声明文件以.d.ts为后缀
  • @type引入第三方声明文件:npm install @types/jquery –save-dev,可以统一管理第三方库的声明文件只需在插件名称前加上@type
  • declare var/let/const 声明全局变量,一般全局变量都是禁止修改的常量,所以大部分情况都应该使用 const 而不是 var / let
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明全局对象(含有子属性)
  • interface 和 type 声明全局类型

元祖:数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象

1
2
3
4
5
6
let tup: [string, number] = ['first', 15];  // 定义,定义时也可不赋值
tup = ['second', 30] // 直接赋值
tup[0] = 'second' // 通过索引赋值
tup = ['second'] // 报错,需要提供所有元组类型中指定的项
tup.push(20); // 添加越界元素
tup.push(true); // 报错,越界元素类型会被限制为元组中每个类型的联合类型

枚举:enum关键字用于限定取值在一定范围内

普通枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 枚举成员会被赋值为从 0 开始递增的数字
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days.Sun === 0); // true
console.log(Days[0] === "Sun"); // true

// 当我们手动赋值时,未被赋值的枚举项会接着上一个枚举项 +1 递增,手动赋值的枚举项也可以为小数或负数
enum Days {Sun = 7, Mon = 8, Tue, Wed, Thu = 5.5, Fri, Sat};
console.log(Days.Mon === 8); // true
console.log(Days.Wed === 10); // true
console.log(Days.Fri === 6.5); // true

// 手动赋值的枚举项可以不是数字,需要使用类型断言来让 tsc 无视类型检查,非数字枚举项后面不能跟未手动赋值的枚举项,因为无法获得初始值
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};

// 当枚举项是计算所得项时,后面不能跟是未手动赋值的项
enum Color {Red, Green, Blue = "blue".length};

常数枚举:const enum 关键字定义,常数枚举会在编译阶段被删除,并且不能包含计算成员

1
const enum Directions {Up, Down, Left, Right}

外部枚举:是指使用 declare enum 定义的枚举类型,只会用于编译时的检查,编译结果中会被删除

1
declare enum Directions {Up, Down, Left, Right}

类:基于es6 class 和 es7 新提案的基础上添加了三种访问修饰符

*public:修饰的属性或方法是公有的,任何地方都能被访问
*private:修饰的属性或方法是私有的,不能在声明它的类的外部访问
*protected:和 private 类似,区别是它在子类中允许被访问

泛型:定义函数、接口或类的时候,使用的时候再指定类型的一种特性

vs code 提示:Experimental support for decorators is a feature that is subject to change in a future release. Set the ‘experimentalDecorators’ option to remove this

在 tsconfig.json 文件中修改规则 experimentalDecorators

学习资源

官网 http://www.typescriptlang.org/
对新手比官网更友好些 https://ts.xcatliu.com/introduction/what-is-typescript.html
vue + ts项目实战 https://juejin.im/post/5b54886ce51d45198f5c75d7#heading-14