一、TypeScript 是什么
TypeScript 是一种由微软开发的自由和开源的编程语言。于2012年推出。
TypeScript 是 JavaScript 的一个超集。为 JavaScript 添加了类型系统。
- TypeScript 与 JavaScript 的区别
TypeScript JavaScript
JavaScript的超集用于解决大型项目的代码复杂性 一种脚本语言,用于创建动态网页
可以在编译期间发现并纠正错误 作为一种解释型语言,只能在运行时发现错误
强类型、支持静态和动态类型 弱类型,没有静态类型
最终被编译成 JavaScript 代码,使浏览器可以理解 可以直接在浏览器中使用 - 获取 TypeScript
- 下载 TypeScript
● 全局安装
○ npm install -g typescript
○ tsc -v
● 局部安装
○ npm install typescript
○ npx tsc -v - 验证 TypeScript
使用 tsc -v 命令能输出版本号即可。
tsc -v
Version 4.9.3
使用 tsc -h 命令查看帮助文档
tsc -h
3. 编译 TypeScript 文件
tsc hello.ts
hello.ts => hello.js
tsc hello.ts --outfile a.js
hello.ts => a.js
tsc hello.ts --outdir dist
hello.ts => dist/hello.js
-
典型 TypeScript 工作流程
-
TypeScript 初体验
function greet(person: string) {
returnHello ${name}
}
console.log(greet(“TypeScript”));
执行 tsc hello.ts 命令,之后会生成一个编译好的文件 hello.js
function greet(person) {
return "Hello ".concat(name);
}
console.log(greet(“TypeScript”));
类型擦除:粗略地说,一旦 TypeScript 的编译器完成了对代码的检查,它就会擦除类型来生成“编译”的代码。 这意味着一旦你的代码被编译,生成的纯JS代码就没有类型信息。
二、基元类型:String、Number、Boolean
类型注解 :string 或者 : string(有无空格不影响)
注意:类型定义好后不能修改
let str1: string = “hello”;
str1 = “world”; // ok
str1 = 123; // error
- String 类型
let firstName: string = “张三”;
let message: string =Hello ${firstName}, age is ${age}
; - Number 类型
let age: number = 18; - Boolean 类型
let isDone: boolean = false; - BigInt 类型
let oneHundred: bigint = BigInt(100); - Symbol 类型
let s: symbol = Symbol();
三、Array 与 Tuple - Array 类型
// 第一种定义数组类型的方式: type[] ,例如 string[] 、 number[]
let arr1: number[] = [1, 2, 3, 4];
// 第二种定义数组类型的方式(数组泛型):Array,例如 Array、Array
let arr2: Array = [1, 2, 3, 4]; - Tuple 类型
数组一般由同种类型的值组成,但有时我们需要在数组中储存不同类型的值,这时候我们就可以使用元组。
元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let x: [string, number];
x = [“hello”, 1]; // OK
x = [1, “hello”]; // Error
console.log(x[0]); // 输出 hello
四、Any 与 Unknown - Any 类型
Any类型会丢失代码提示
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称为全局超级类型)
作为开发者,any 类型给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查:
// 声明一个 any 类型的变量
let notSure: any = 4;
// 可以给它赋值为 其他任何类型
notSure = “maybe it is a string”; // OK
notSure = true; // OK
// 调用它不存在的属性or方法
notSure.myName; // Ok
notSure.getName(); // OK
// 将它赋值给任意类型的
let num: number = notSure // OK
在许多情况下,这太宽松了。使用 any 类型,可以很容易编写类型正确但在运行时可能有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any 带来的问题,TypeScript 3.0 引入了类型安全的 unknown 类型。 - noImplicitAny 配置
如果未指定类型,并且 TypeScript 无法从上下文推断出类型,则编译器通常默认为。any
通常希望避免这种情况,因为未进行类型检查。这时可以 noImplicitAny 配置
- 使用 tsc --init 命令生成 tsconfig.json 文件。该文件是 ts 的配置文件
tsc --init - 默认 tsconfig.json 中已启用了严格检查模式 strict
- Unknown 类型
就像所有类型都可以赋值给 any, 所有类型也都可以赋值给 unknown 。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)
let value: unknown;
// 任意类型都可以分配给 unknown 类型
value = true; // ok
value = 40; // ok
value = “hello world”; // ok
value = []; // ok
value = {}; // ok
value = null; // ok
value = undefined; // ok
value = new TypeError(); // ok
value = Symbol(); // ok
只能将 unknown 类型分配给 any 类型和 unknown 类型。
let value: unknown;
let value1: unknown = value; // ok
let value2: any = value; // ok
let value3: number = value; // Error
let value4: string = value; // Error
对 unknown 类型的值做一些操作时,这些操作将不再被认为是类型正确的。
// 声明一个 unknown 类型的变量
let notSure: unknown = 4;
// 可以给它赋值为 其他任何类型
notSure = “maybe it is a string”;
notSure = true;
// 不同于 any, 读取任意属性或方法都会报类型错误,避免运行时的错误问题
str3.split(0,1); //error
str3.length; // error
// 调用它不存在的属性or方法
notSure.myName; // Error
notSure.getName(); // Error
// 将它赋值给任意类型
let num: number = notSure; // Error
Any & Unknown ⭐
共同点:
- any 和 unknown 都是 TypeScript 顶级类型
- 都可以接收任意类型的值
- 都丢失了代码提示
不同点: - unknown 是类型安全的,any 是类型不安全的
如何理解类型安全还是不安全?
类型安全主要是指能够在编写代码时就能及时发现一些后续运行时隐藏的问题
五、类型推导
TypeScript 能根据一些简单的规则推断(检查)变量的类型。故大部分情况下我们无需手动标注类型。
let foo = 123; // foo 是 ‘number’
let bar = ‘hello’; // bar 是 ‘string’
foo = bar; // Error: 不能将 ‘string’ 赋值给 number
六、函数
函数是在 JavaScript 中传递数据的主要方式。 TypeScript 允许您指定函数的输入和输出值的类型。
- 参数类型标注
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + “!!”);
}
greet(“hello”); // OK
greet(42); // Error 类型“number”的参数不能赋给类型“string”的参数。
2. 可选参数
/**
- 通过在 参数名称后面写一个 ? 号,表示该参数可选。
- 这时 name 的类型为 string | undefined 的一个联合类型
*/
function greet(name?: string) {
// TODO
}
greet(“hello”); // OK
greet(); // OK
greet(42); // Error 类型“number”的参数不能赋给类型“string”的参数
3. 返回类型标注
函数没有标注返回值类型,默认的返回值类型是 void
function getFavoriteNumber(): number {
// return 26; // OK
return “hello”; // Error 不能将类型“string”分配给类型“number”。
}
4. 匿名函数自动推断类型
const names = [“Alice”, “Bob”, “Eve”];
names.forEach(function (s) {
console.log(s.toUppercase()); // Error 属性“toUppercase”在类型“string”上不存在。你是否指的是“toUpperCase”?
});
names.forEach((s) => {
console.log(s.toUppercase()); // Error 属性“toUppercase”在类型“string”上不存在。你是否指的是“toUpperCase”?
});
5. 参数默认值设置
function fn1(name: string = “张三”, age?: number){}
七、对象类型
要定义对象类型,我们只需列出其属性及其类型
// key 的分割可以是,也可以是;(推荐使用;)
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate’s x value is " + pt.x);
console.log("The coordinate’s y value is " + pt.y);
}
printCoord({ x: 3, y: 7 }); // OK
printCoord({ x: “3”, y: “8” }); // Error 不能将类型“string”分配给类型“number”
- 可选属性
对象类型还可以指定其部分或全部属性是可选的。 只需要在属性名称后添加 ? 号即可。
function printName(obj: { first: string; last?: string }) {
// …
}
printName({ first: “Bob” }); // OK
printName({ first: “Alice”, last: “Alisson” }); // OK
2. 可选属性检查
当您从可选属性读取时,您必须在使用之前进行检查。
function printName(obj: { first: string; last?: string }) {
console.log(obj.last.toUpperCase()); // Error 对象可能为“未定义”
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// OK
console.log(obj.last?.toUpperCase());
}
类型缩窄
// 1. 直接使用 ?. 可选链语法
person.age?.toFixed();
// 2. 类型缩窄 if
if(person.age){
person.age.toFixed();
}
// 3. 类型缩窄 &&
person.age && person.age.toFixed();
// 4. 类型缩窄 三元运算
person.age ? person.age.toFixed() : undefined;
3. 只读属性
对象类型还可以指定其部分或全部属性是只读的。只需要在属性名称前添加 readonly 即可。
function printName(obj: { readonly first: string; last?: string }) {
obj.first = “hello”; // Error 无法分配到 “first” ,因为它是只读属性。
}
八、联合类型
联合类型是由两个或多个其他类型组成的类型,通常使用 | 联合。
- 定义联合类型
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
printId(100); // OK
printId(“200”); // OK
printId(true); // Error 类型“boolean”的参数不能赋给类型“string | number”的参数
let isOk: 0 | 1 = 2; // 不能将类型“2”分配给类型“0 | 1”
2. 使用联合类型
在 TypeScript 中,若一个变量使用了联合类型,那么当我们使用该变量时必不可少的会去明确的限制该变量的具体类型,这称为类型缩小。
function printId(id: number | string) {
console.log(id.toUpperCase()); // Error
// 类型缩小
if (typeof id === “string”) {
// In this branch, id is of type ‘string’
console.log(id.toUpperCase());
} else {
// Here, id is of type ‘number’
console.log(id);
}
}
九、类型别名 type
将现有的一些类型取个别名,方便后续复用。
- 描述对象
type Point = {
x: number;
y: number;
};
function printCoord(pt: Point) {
console.log("The coordinate’s x value is " + pt.x);
console.log("The coordinate’s y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
2. 描述函数
type SetPoint = (x: number, y: number) => void;
3. 描述联合类型
type ID = number | string;
十、接口 interface
接口声明是命名对象类型的另一种方法
interface 的名字,一般推荐以大写 8I 开头
- 描述对象类型
声明的接口可以多次使用
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("The coordinate’s x value is " + pt.x);
console.log("The coordinate’s y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
2. 描述函数
interface SetPoint {
(x: number, y: number): void;
}
十一、type 和 interface ⭐
- 相同点
- 类型别名和接口都可以用来描述对象或函数
- 类型别名和接口都支持多个扩展
// 多个拓展 写法
type Dog = {
sex: 0 | 1;
}
type Bear = Animal & Dog & {
honey: boolean;
}
interface Dog2 {
sex: 0 | 1;
}
interface Bear extends Animal, Dog2 {
honey: boolean;
}
2. 不同点
-
类型别名可以为基本类型、联合类型或元组类型定义别名,而接口不行。
-
同名接口会自动合并,而类型别名不会。
-
使用场景
-
使用类型别名的场景:
a. 定义基本类型的别名时,使用 type
b. 定义元组类型时,使用 type
c. 定义函数类型时,使用 type
d. 定义联合类型时,使用 type
e. 定义映射类型时,使用 type -
使用接口的场景:
a. 需要利用接口自动合并特性的时候,使用 interface
b. 定义对象类型且无需使用 type 的时候,使用 interface
十二、TypeScript 断言 -
类型断言
有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方法可以告诉编译器,”相信我,我知道自己在干什么“。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
类型断言有两种方式:
- ”尖括号“ 语法
(str)
let someValue: any = “this is a string”;
let strLength: number = (someValue).length; - as 语法
(str as string[])
let someValue: any = “this is a string”;
let strLength: number = (someValue as string).length; - 非空断言
非空断言使用一个后缀表达式操作符!表示。可以用于断言操作对象是非 null 和 非 undefined 类型的。 - 忽略 null 和 undefined 类型
function myFunc(maybeString: string | null | undefined) {
const str: string = maybeString; // 不能将类型“string | null | undefined”分配给类型“string”。不能将类型“undefined”分配给类型“string”
const str1: string = maybeString!; // OK
console.log(str);
console.log(str1);
}
2. 调用函数时忽略 undefined 类型
type CallbackType = () => number;
function myFunc(callback: CallbackType | undefined) {
const num1 = callback(); // Error 不能调用可能是“未定义”的对象。
const num2 = callback!(); // Ok
}
十三、文本类型
除了一般类型之外,我们还可以在类型位置引用特定的字符串和数字
let isOk: 0 | 1 = 2; // 不能将类型“2”分配给类型“0 | 1”
let string: “hello” = “”; // Error 不能将类型““””分配给类型““hello””
十四、Null 和 Undefined 类型
TypeScript 中,undefined 和 null 两者都有各自的类型分别为 undefined 和 null。
let u: undefined = undefined;
let n: null = null;
十五、Enum 类型(枚举)
使用枚举我们可以定义一些带名字的常量。使用枚举可以清晰地表达意图或创建一组有区别的用例。
TypeScript 支持数字和字符串的枚举。
- 数字枚举
enum Direction {
Up,
Down,
Left,
Right,
}
let dir: Direction = Direction.Up;
/**
- 默认情况下,Up 的值为 0, 其余成员会按顺序自增。
- Up 0
- Down 1
- Left 2
- Right 3
*/
enum Direction {
Up = 1,
Down,
Left,
Right,
}
let dir: Direction = Direction.Up;
/**
- 也可以给成员设置初始值。
- Up 1
- Down 2
- Left 3
- Right 4
*/
编译生成的 JS 代码如下示例:
var Direction;
(function (Direction) {
Direction[Direction[“Up”] = 1] = “Up”;
Direction[Direction[“Down”] = 2] = “Down”;
Direction[Direction[“Left”] = 3] = “Left”;
Direction[Direction[“Right”] = 4] = “Right”;
})(Direction || (Direction = {}));
var dir = Direction.Up;
- 字符串枚举
在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量。
enum Direction {
Up = “UP”,
Down = “DOWN”,
Left = “LEFT”,
Right = “RIGHT”,
}
let dir: Direction = Direction.Up;
编译生成的 JS 代码如下示例:
var Direction;
(function (Direction) {
Direction[“Up”] = “UP”;
Direction[“Down”] = “DOWN”;
Direction[“Left”] = “LEFT”;
Direction[“Right”] = “RIGHT”;
})(Direction || (Direction = {}));
var dir = Direction.Up;
反向映射:
通过观察数字枚举和字符串枚举的编译结果,我们可以知道数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:
enum Direction {
Up,
Down,
Left,
Right,
}
let dirName = Direction[0]; // Up
let dirValue = Direction.Up; // 0
3. 常量枚举
除了数字枚举和字符串枚举之外,还有一种特殊的枚举 – 常量枚举。它是使用 const 关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生产任何 JavaScript 。
const enum Direction {
Up,
Down,
Left,
Right,
}
let dir: Direction = Direction.Up;
编译生成的 JS 代码如下:
var dir = 0 /* Direction.Up */;
4. 异构枚举
异构枚举的成员值是数字和字符串的混合
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = “YES”,
}
十六、 Void 类型 与 Never 类型
- Void 类型
某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。
当一个函数没有返回值时,这时它的返回值类型是 void
// 返回没有返回值时,hello 返回值类型为 void
function hello(name: string) {
console.log(Hello ${name}
);
}
// 但当你主动返回 undefined 时,返回值类型为 undefined
function world(name: string) {
console.log(Hello ${name}
);
return undefined;
}
2. Never 类型
never 类型表示的是那些永不存在的类型。
// 返回 never 的函数必须存在无法达到的终点
function error(message: string): never {
// 1. 通过抛出异常实现 返回值类型为 never
throw new Error(message);
}
function infiniteLoop(): never {
// 2. 通过死循环实现 返回值类型为 never
while (true) {}
}
// 类型别名 a 是一个 never 类型。因为不存在一个类型既是 string 又是 number
type a = string & number