一、TypeScript是什么
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JS 的一个超集,而且向这个语言添加了可选的静态类型和基于类的面向对象编程。
也就是说,ts 包含兼容所有JS的特性,支持共存与渐进式引入;同时,它的可读性、可维护性增强,在多人合作的大型项目中,获得更好的稳定性和开发效率。
1.1 TypeScript 与 JavaScript的区别
TypeScript | JavaScript |
---|---|
Js 的超集用于解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页 |
可以在编译期间发现并纠正错误 | 作为一种解释型语言,只能在运行时发现错误 |
弱类型,支持静态和动态类型 | 弱类型,没有静态类型选项 |
最终被编译成JS代码,使浏览器理解 | 可以直接在浏览器中使用 |
支持模块、泛型和接口 | 不支持模块、泛型和接口 |
1.2 TypeScript 的工作流程
1.3 TypeScript初体验
新建一个hello.ts
文件,并输入以下内容:
function greet(person: string) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
然后执行tsc hello.ts
命令,之后会生成一个编译好的文件hello.js
:
"use strict";
function greet(person) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
可以看到,在上述的输出结果中,person
参数的类型信息在编译后被檫除了。TypeScript 只会在编译阶段对类型进行静态检查,如果发现错误,编译时就会报错,而在运行时,编译生成的JS与普通的JS文件一样,并不会进行类型检查。
二、TypeScript基础类型
2.1 常见类型
// Boolean类型
let isDon: boolean = false;
// Number类型
let count:number = 10;
// String类型
let name:string = 'btqf';
// Undefined类型
let u: undefined = undefined;
// Null类型
let n: null = null;
// Symbol类型
const sym = Symbol();
let obj = {
[sym]: "btqf",
};
console.log(obj[sym]); // btqf
// Array类型
let list:number[] = [1,2,3]
let list:Array<number> = [1,2,3]; // Array<number>泛型写法
2.2 Enum 类型
使用枚举我们可以定义一些带名字的变量。使用枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript支持数字和基于字符串的枚举。
1.数字枚举
enum Directionn {
North,
South,
East,
West,
}
let dir: Direction = Direction.North;
默认情况下,North的初始值为0,其余的成员从1开始自动增长。换句话说,Direction.South = 1
,Direction.East = 2
…
以上的枚举示例经编译后,对应的 ES5 代码如下:
"use strict";
var Direction;
(function (Direction) {
Direction[(Direction["NORTH"] = 0)] = "NORTH";
Direction[(Direction["SOUTH"] = 1)] = "SOUTH";
Direction[(Direction["EAST"] = 2)] = "EAST";
Direction[(Direction["WEST"] = 3)] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;
当然我们也可以设置North
的初始值,比如:
enum Directionn {
North = 2,
South,
East,
West,
}
2.字符串枚举
在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
以上代码对应的ES5代码如下:
"use strict";
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
通过观察数字枚举和字符串枚举的编译结果,我们可以知道数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射。
3.常量枚举
它是用const
关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生成任何JavaScript。
const enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
以上代码对应的ES5代码为:
"use strict";
var dir = 0 /* NORTH */;
4.异构枚举
异构枚举成员值是数字和字符串的混合:
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
以上代码对于ES5代码如下:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
2.3 Any类型
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。TypeScript允许我们对any
类型的值执行任何操作,而无需实现执行任何形式的检查。
let value: any;
value = 666;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
当我们使用any
类型,可以很容易地编写类型正确但在运行时有问题的代码,但这也就无法使用TypeScript提供的大量的保护机制。为了解决这个问题,TypeScript 3.0 引入了unknown
类型。
2.4 Unknown 类型
unknown
为 TypeScript 类型系统的另一种顶级类型。
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
对value
变量的所有赋值都被认为是类型正确的,但是当我们将类型为unknown
赋值给其他变量时,它只能赋值给any
类型和unknown
类型本身。
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
当我们将value
类型设置为unknown
后,这些操作都不在被认为是类型正确的,通过将any
类型变为unknown
类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。
2.5 Tuple类型
众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。
元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。请看下面的例子进行理解:
let tupleType: [string, boolean];
tupleType = ["btqf", true];
同时,我们必须得要注意,在元组初始化的时候,类型要逐个匹配并且提供每个属性的值。
2.6 Void类型
某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:
// 声明函数返回值为void
function warnUser(): void {
console.log("This is my warning message");
}
需要注意的是,在严格模式下,void类型的变量的值只能为undefined
.
2.7 object, Object类型 和 {} 类型
1.object类型
object类型用于表示非原始类型
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
create(o: object | null): any;
// ...
}
const proto = {};
Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // Error
Object.create(1337); // Error
Object.create(true); // Error
Object.create("oops"); // Error
2.Object类型
Object 类型:它是所有 Object 类的实例的类型,它由以下两个接口来定义:Object.prototype
和ObjectConstructor
,前者定义原型对象上的属性,后者定义Object
类的属性。
3.{}类型
{} 类型描述了一个没有成员的对象。当你试图访问这样一个对象的任意属性时,TypeScript 会产生一个编译时错误。只不过仍然可以使用在Object类型上定义的所有属性和方法。
const obj = {};
// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker";
// "[object Object]"
obj.toString();
2.8 never类型
never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,通过 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。
type Foo = string | number;
// type Foo = string | number | boolean;
// 当我们修改了Foo的类型,将会发生编译错误, 在下述中boolean无法赋值给never类型
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
三、TypeScript断言
3.1 类型断言
有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型.
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
let someValue: any = "this is a string";
// 尖括号 语法
let strLength: number = (<string>someValue).length;
// as 语法
let strLength: number = (someValue as string).length;
3.2 非空断言
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined
function myFunc(maybeString: string | undefined | null) {
// Type 'string | null | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
const onlyString: string = maybeString; // Error
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}
3.3确定赋值断言
确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error
function initialize() {
x = 10;
}
在上述的例子中,异常信息显示变量 x 在赋值前被使用了,要解决该问题,可以使用确定赋值断言:
let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
x = 10;
}
四、类型守卫
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以确保一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:
4.1 in关键字
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
4.2 typeof关键字
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof
类型保护只支持两种形式:typeof v === 'username'
和typeof v !== typename
,"typename"
必须是number
/string
/boolean
/symbol
。但是TypeScript并不会阻止你与其他字符串比较,语言不会把那些表达式识别为 类型保护。
4.3 instanceof关键字
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
4.4 自定义类型保护的类型谓词
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
五、联合类型和类型别名
5.1 联合类型
联合类型通过与null
或undefined
一起使用:
const sayHello = (name: string | undefined) => {
/* ... */
};
sayHello("semlinker");
sayHello(undefined);
也就是说,类型A和类型B联合后的类型是同时接受A和B值的类型。
let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';
以上示例中的 1、2
或 'click'
被称为字面量类型,用来约束取值只能是某几个值中的一个。
5.2 可辨识联合
TypeScript 可辨识联合型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
5.3 类型别名
类型别名用来给一个类型起个新名字。
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
六、交叉类型
在TypeScript中交叉类型是将多个类型合并为一个类型。通过&
运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
type PartialPointX = { x: number };
type Point = PartialPointX & { y: number };
let point: Point = {
x: 1,
y: 1
}
6.1 同名基础类型属性合并
当合并多个类型时,刚好出现某些类型存在相同的成员,但对应的类型不一致,此时混入后的类型为never
interface X {
c: string;
d: string;
}
interface Y {
c: number;
e: string
}
type XY = X & Y;
type YX = Y & X;
let p: XY;
let q: YX;
p = { c: 6, d: "d", e: "e" };
q = { c: "c", d: "d", e: "e" };
如上述代码,混入后c
的类型为string & number
,很明显这种类型是不存在的,所以混入后 c 的类型为never
.
6.2 同名非基础类型属性合并
在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }
interface A { x: D; }
interface B { x: E; }
interface C { x: F; }
type ABC = A & B & C;
let abc: ABC = {
x: {
d: true,
e: 'semlinker',
f: 666
}
};
console.log('abc:', abc);
七、TypeScript函数
7.1 TS函数与JS函数的区别
TypeScript | JavaScript |
---|---|
含有类型 | 无类型 |
箭头函数 | 箭头函数ES6 |
函数类型 | 无函数类型 |
必填和可选参数 | 所有参数都是可选的 |
默认参数 | 默认参数 |
剩余参数 | 剩余参数 |
函数重载 | 无函数重载 |
7.2 参数类型和返回类型
function createUserId(name: string, id: number): string {
return name + id;
}
7.3 函数类型
let IdGenerator: (chars: string, nums: number) => string;
function createUserId(name: string, id: number): string {
return name + id;
}
IdGenerator = createUserId;
7.4 可选参数
在声明函数时,可以通过 ? 号来定义可选参数,比如 age?: number
这种形式。在实际使用时,需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。
// 可选参数
function createUserId(name: string, id: number, age?: number): string {
return name + id;
}
7.5 函数重载
函数重载或方法是使用相同名称和不同参数数量或类型创建多个方法的一种能力。
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
// type Combinable = string | number;
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}