前言

TS入门教程,从0到1

TS介绍

TS是什么?

ts是js的超集,超集指的是他包含了es前面所有版本的内容并有自己的拓展。

TS做到了什么?

首先js是弱类型,很多错误在运行的时候才能发现。
ts使用了他的静态类型检测机制(后面提及)帮助我们提早发现错误。

TS特点

  1. 支持最新的js特性(超集)
  2. 静态代码检查(解决弱类型)
  3. 有其他后端语言的特性(枚举,泛型,类型转化,命名空间,声明文件…)

静态类型检测

为什么会产生这个问题?

js在运行代码的时候,需要我们人为的知道代码的返回值是什么,才能做出响应的操作(执行函数才能知道)。不能在我们书写的过程就立刻给出编译错误的反馈。
ts就能在(编译之前)告诉我们

非异常故障

ts不仅能告诉我们哪些函数的值错误,还能识别类似于错别字,未调用函数和基本逻辑错误

1
2
3
4
const user={
name:'ber'
}
user.location

上面这段代码在js中返回undefined,而在ts中提前告诉你这个变量没有定义不能使用

错别字:告诉你对象上面某个方法拼写错误等

未调用函数:函数没有加括号就使用,会提示未调用错误

逻辑错误:比如只有两种条件的情况下,if一个条件又elseif另外一个条件,就会报错

搭建环境

安装ts

1
npm i -g typescript

安装ts-node

可以直接编译ts文件

1
2
3
npm i -g ts-node
//使用
npx ts-node xxx.ts

创建一个tsconfig.json

解决变量名or函数名相同报错的问题

1
tsc --init

官方playground

官方提供的ts在线编译云环境—playground

编译

在对应的目录终端运行

1
tsc hello.ts

得到对应的js文件,执行node命令就可以执行js代码

ts和js的冲突

当我们将ts代码修改为一个函数并调用的时候,再次编译运行js文件,会产生冲突

原因:函数名or变量名相同,重复定义

解决方法:

  1. tsc --init生成配置文件
  2. 注释掉"strict": true, o9

自动编译

tsc --watch

发出错误

我们在ts里面报了错,但是在编译完之后,还是能运行js文件,如果我们要优化这个过程,结果为:ts编译报错就不生成js文件的话,需要如下:

tsc -noEmitOnError hello.ts

or

tsc -noEmitOnError --watch

降级编译

设置json里面的target为es5

严格模式

不同的用户希望在ts里面检查的严格程度是不一样的,配置如下

strict是包含了下面两个类型的模式

noImplicitAny是指不可以忽略any,也就是要定义类型

strictNullChecks意思是null undefined只能赋值给对应的类型

基础数据类型

显式类型

ts给我们提供了类型定义的注释,比如说

1
2
3
4
function conflit(person:string,date:Date){
console.log(`hello,${person},${date}`)
}
conflit('jojo',new Date());

但是在编译成js之后就会删去,可以方便程序员查看。

根据上面显式类型的说法我们知道了ts可以定义很多数据类型,比如

1
2
3
4
5
6
7
8
let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
let sym: symbol = Symbol("me");

几个需要留意的数据

当我们定义好类型的时候,如果给变量赋值别的类型是会报错提示的,但是有两个类型不一样

null&undefined

null undefined这两个类型可以作为其他所有类型的子类型,也就是可以把它们赋给别的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// null和undefined赋值给string
let str:string = "666";
str = null
str= undefined

// null和undefined赋值给number
let num:number = 666;
num = null
num= undefined

// null和undefined赋值给object
let obj:object ={};
obj = null
obj= undefined

// null和undefined赋值给Symbol
let sym: symbol = Symbol("me");
sym = null
sym= undefined

// null和undefined赋值给boolean
let isDone: boolean = false;
isDone = null
isDone= undefined

// null和undefined赋值给bigint
let big: bigint = 100n;
big = null
big= undefined

但如果在tsconfig.json里面指定了"strictNullChecks":true那么它们只能被赋值给自己的类型或者void

number&bigint

这两个值互不兼容,也就是说不能相互赋值,会报错

Array

对数组的定义有两种方式

1
2
3
4
//arr:type[]
let arr:string[] = ["1","2"];
//arr:Array<type>这是泛型的定义方法
let arr2:Array<string> = ["1","2"]

定义联合类型数组

1
2
3
//arr:(type1|type2)[]
let arr3:(number | string)[];
arr3 = ["1","sdcas",1,2];

定义对象类型数组

1
2
3
4
5
interface Arrobj {
name:string,
age:number
}
let arr4:Arrobj[] = [{name:'jojo',age:18}]

any

定义成any类型的时候表示你不希望某个特定值导致类型错误的产生。

注意避免使用any 这样会丢失ts的保护机制

函数

函数声明

具名函数

1
2
3
function sum(x: number,y: number): number {
return x+y;
}

这个是告诉我们函数的返回值也可以指定类型,但一般不需要,因为ts会给我们自动判断返回值的类型

匿名函数:会自动根据上下文判断读取的数据类型是什么。

函数表达式

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

用接口定义函数类型

1
2
3
interface SearchFunc{
(source: string, subString: string): boolean;
}

采用函数表达式接口定义函数类型的数据的时候,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数,参数类型,返回值类型不变。

可选参数

使用变量名?: type的形式

1
2
3
4
5
6
7
8
9
function myFun(x: number,y?: number){
if(y){
return x+y;
}else{
return x;
}
}
console.log(myFun(1,2))
console.log(myFun(33))

注意可选参数后面不能再跟必需参数

参数默认值

在type后面加= 默认值

1
2
3
4
5
function myFun(x: number, y: number = 22) {
return x + y;
}
console.log(myFun(1, 2))
console.log(myFun(33))

剩余参数

注意需要用any来指定rest

1
2
3
4
5
6
7
8
function myFun(arr: Array<number>, ...items: any[]) {
items.forEach((item)=>{
arr.push(item);
})
return arr;
}
let arr: Array<number> = [1,2,3];
console.log(myFun(arr,3,4,543414,123))

函数重载

js是动态语言,会根据不同的参数返回不同类型的调用结果

1
2
3
4
5
function add(x, y) {
return x + y;
}
add(1, 2); // 3
add("1", "2"); //"12"

but如果开启了noImplicitAny的话,ts不能用上述代码,因为需要指定类型,所以就诞生了联合类型帮助我们根据不同的情况返回不同的结果

1
2
3
4
5
6
7
8
9
10
11
type Combination = string | number;
function myFun(x: Combination, y: Combination) {
if (typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString();
} else {
return x + y;
}
}
console.log(myFun(1,3));
console.log(myFun(1,"3"));

但其实到这一步,看似没有问题了,实际上新的问题又产生了

1
2
3
4
5
6
7
8
9
10
11
type Combination = string | number;
function myFun(x: Combination, y: Combination) {
if (typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString();
} else {
return x + y;
}
}
const result = myFun("abc","def");
console.log(result);
result.split(" ");//报错

查看提示可以知道number类型上面没有方法split,为什么是number类型呢?因为我们这个参数虽然定义了组合类型,但是我们的ts并不知道返回值是什么类型,所以产生了函数重载

函数重载就是使用相同函数名称和不同参数类型或不同参数个数创建方法的一种能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Combination = string | number;
function myFun(x: number, y: number): number;
function myFun(x: string, y: number): string;
function myFun(x: number, y: string): string;
function myFun(x: string, y: string): string;

function myFun(x: Combination, y: Combination) {
if (typeof x === 'string' || typeof y === 'string') {
return x.toString() + y.toString();
} else {
return x + y;
}
}
const result = myFun("abc", "def");
console.log(result);
const res = result.split("b");
console.log(res)

上述实现了函数的重载

对象

1
2
3
4
function PrintCoord(pt:{x: number,y: number}){
console.log(pt.x,pt.y);
}
PrintCoord({x:333,y:44})

配合可选参数+可选链的例子

1
2
3
4
function PrintCoord(pt:{x: number,y?: string}){
console.log(pt.y?.toUpperCase())
}
PrintCoord({x:333,y:'jojo'})

接口

我们定义接口可以用于类型的后续拓展,看基础应用如下

1
2
3
4
5
6
7
8
interface Point {
x: number,
y: number
}
function myFun(pt:Point){
return pt.x+pt.y;
}
console.log(myFun({x:1,y:2}))

拓展接口如下:

1
2
3
4
5
6
7
8
9
10
11
interface Animal {
name: string
}
interface Bear extends Animal{
honey: string
}
const bear: Bear = {
name:'jobear',
honey:'蜂蜜'
}
console.log(bear)

type的拓展 通过&符号

1
2
3
4
5
6
7
8
9
10
11
type Animal = {
name: string
}
type Bear = Animal &{
honey: string
}
const bear: Bear = {
name:'jobear',
honey:'蜂蜜'
}
console.log(bear)

向现有的类型添加字段(重复定义接口名字添加)

1
2
3
4
5
6
7
8
9
10
11
interface jojo {
name: string
}
interface jojo {
age: number
}
const j: jojo = {
name: 'jojo',
age: 18
}
console.log(j)

类型创建后不能更改

1
2
3
4
5
6
7
type jojo = {
name: string
}
type jojo = {
age: number
}
//报错

void

void表示没有任何类型,一般的用处是函数没有返回值的时候定义为void

1
2
3
4
function myFunc(): void{
console.log('this is a void function')
}
myFunc()

never

never表示永不存在值的类型

比方说当函数遇到异常的时候,那么这个函数永远都不会有返回值,因为抛出异常直接中断了程序运行,不会到最后一步。

或者说代码中遇到无限循环的时候,程序永远到不了执行函数返回值的那一步,永不存在返回

使用的场景是:
针对于我们的代码来说,如果我们定义的联合类型被修改了,那么会因为我们事先在条件判断中写了never从而报错,因此在代码的安全性来讲never是比较可靠的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
//如果有人修改了type的类型 将报错

unknown

unknown定义的类型和any一样可以赋全部值

不要弄混原始类型和对象类型

我们在初学ts的时候,很容易把number,boolean这一类和Number Boolean搞混,实际上前者属于原始类型,后者属于原始类型的包装对象,在使用上面,前者可以赋值给后者,后者不能赋值给前者

1
2
3
4
let num: number;
let Num: Number;
Num = num; // ok
num = Num; // ts(2322)报错

元组

元组用来限制数组元素的个数和类型,是ts独有的机制,适合用于多值返回

元组定义的数据必须类型和个数都要匹配

1
2
3
4
5
6
let x: [string, number]; 
// 类型必须匹配且个数必须为2

x = ['hello', 10]; // OK
x = ['hello', 10,10]; // Error
x = [10, 'hello']; // Error

元组的解构赋值

元组也是支持解构赋值的

1
2
3
4
let employee: [number, string] = [1, "Semlinker"];
let [id, username] = employee;
console.log(`id: ${id}`);//id 1
console.log(`username: ${username}`);// username Semlinker

元组类型的可选元素

见例子知用法

1
2
3
4
5
6
7
8
9
type Point = [number, number?, number?];

const x: Point = [10]; // 一维坐标点
const xy: Point = [10, 20]; // 二维坐标点
const xyz: Point = [10, 20, 10]; // 三维坐标点

console.log(x.length); // 1
console.log(xy.length); // 2
console.log(xyz.length); // 3

元组的剩余元素

1
2
3
4
type RestTupleType = [number, ...string[]];
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];
console.log(restTuple[0]);
console.log(restTuple[1]);

元组的只读类型

1
2
3
4
5
6
7
8
9
10
const point: readonly [number, number] = [10, 20];

// Cannot assign to '0' because it is a read-only property.
point[0] = 1;
// Property 'push' does not exist on type 'readonly [number, number]'.
point.push(0);
// Property 'pop' does not exist on type 'readonly [number, number]'.
point.pop();
// Property 'splice' does not exist on type 'readonly [number, number]'.
point.splice(1, 1);

类型断言

ts提供了as xxx进行类型的断言,这样子我们在自身清楚实际的类型的情况下,让编译器不要干扰你的类型值,就好比any,但是使用了any就完全指定不了了,下面是一个例子

1
2
3
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number;
console.log(greaterThan2)

告诉编译器我要编译出来的结果是什么类型

语法如下:两种类型

1
2
3
4
5
6
7
// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

尖括号语法会和react中的JSX冲突,所以更推荐使用as

非空断言

在上下文中ts的类型检查器无法判断类型的时候,使用!就可以用于断言操作对象是非null和非undefined类型
举例,x!将从x的值域排除null和undefined

eg

1
2
3
4
5
6
7
8
type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}

确定赋值断言

在变量声明后面使用!这个断言,告诉ts这个变量会被明确的赋值

1
2
3
4
5
6
7
let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
x = 10;
}

如果不写该断言,则会报错为该数在赋值前被使用了。

枚举

ts独有的定义方式,js中没有,通过enum来进行定义

枚举会自动帮我们递增

1
2
3
4
5
6
7
8
9
10
11
enum Direaction{
UP=1,
DOWN,
LEFT,
RIGHT
}
console.log(Direaction.UP);
console.log(Direaction.RIGHT);
console.log(Direaction.DOWN);
console.log(Direaction.LEFT);
//1 4 2 3

场景:我们知道数值,但不知道它映射到的枚举类型的哪个名字,就可以使用

1
2
3
4
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];

alert(colorName); // 显示'Green'因为上面代码里它的值是2

类型拓宽

通过ts推断出来的类型就是字面量拓宽

1
2
3
4
5
let str = 'this is string'; // 类型是 string
let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
const specifiedStr = 'this is string'; // 类型是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;

类型缩小

将类型声明细化为更具体的类型的过程我们称之为类型缩小

1
2
3
4
5
6
function padLeft(padding: number | string, input: string): string {
if (typeof padding === 'string') {
return new Array(padding + 1).join(" ") + input;
}
return padding + input
}

真值缩小

使用条件、&&、||、if语句,布尔否定!进行缩小的方式称为真值缩小,常见如下:

  1. 强制类型转换
  2. 使用Boolean
  3. 使用!!,第一个叹号将文字转换为布尔类型,再通过另外一个叹号转成真正的true或者false

等值缩小

ts可以通过真值判断类型,如下

1
2
3
4
5
6
function myFunc(x: number|string, y:boolean|string){
if(x===y){
x.toUpperCase;
y.toUpperCase;
}
}

in缩小

js的in操作符用于确定某个对象是否拥有该名称的属性

instanceof缩小

分配缩小

类型谓词

字面量类型

ts支持三种字面量类型:字符串字面量、数字字面量、布尔字面量

1
2
3
4
5
6
{
let specifiedStr: 'this is string' = 'this is string';
let specifiedNum: 1 = 1;
let specifiedBoolean: true = true;
}

联合类型

使用|符号

1
2
3
4
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'; // OK
myFavoriteNumber = 7; // OK

交叉类型

和联合类型差不多,不过这里的符号用的是&,可以理解为是所有类型都要有,类似于求并集

1
2
3
4
5
6
type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
id: 1,
name: 'name',
age: 18
}

这里引申出一个问题,如果合并的是同名的属性类型会怎么样呢?

情况1 两个同名key 但是类型不同

1
2
3
4
5
6
7
type IntersectionTypeConfict = { id: number; name: string; } 
& { age: number; name: number; };
const mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ts(2322) 错误,'number' 类型不能赋给 'never' 类型
age: 2
};

这种情况下直接pass

情况2 两个同名key 类型相同, 但其中一个是另一个是子类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type IntersectionTypeConfict = { id: number; name: 2; } 
& { age: number; name: number; };

let mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ok
age: 2
};
mixedConflict = {
id: 1,
name: 22, // '22' 类型不能赋给 '2' 类型
age: 2
};


这种情况以子类型为主

情况3 两个同名key 类型不是基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface A {
x:{d:true},
}
interface B {
x:{e:string},
}
interface C {
x:{f:number},
}
type ABC = A & B & C
let abc:ABC = {
x:{
d:true,
e:'',
f:666
}
}

结果是可以合并

泛型函数

泛型可以帮助我们关联参数的值和返回值之间的关系,见下面的例子。

需求:如果我们要实现一个函数 identity,函数的参数可以是任何值,返回值就是将参数原样返回,并且其只能接受一个参数,你会怎么做?

由于其可以接受任意值,也就是说你的函数的入参和返回值都应该可以是任意类型。 现在让我们给代码增加类型声明:

1
2
3
4
5
type idBoolean = (arg: boolean) => boolean;
type idNumber = (arg: number) => number;
type idString = (arg: string) => string;
...

这样下来你每次都要写无穷无尽的代码,很明显是不太合理的,所以引出了泛型这个概念,下面看看泛型的用法:

1
2
3
function identity<T>(arg: T): T {
return arg;
}

其中T是一个抽象类型type,只有在调用它的时候才确定它的值。

除了T以外,还有其他的泛型代表:

  1. K key 表示对象中的键类型
  2. V value 表示对象中的值类型
  3. E element 表示元素类型

其实并不是只能定义一个类型变量,我们可以引入自定义的类型变量如下

1
2
3
4
5
6
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));

也可以简略的调用,编译器会帮我们自动识别。

1
console.log(identity(68, "Semlinker"));

泛型约束

如果想打印出参数的size,直接使用是不行的,因为T只是类型,并不是所有方法的集合

1
2
3
4
function trace<T>(arg: T): T {
console.log(arg.size); // Error: Property 'size doesn't exist on type 'T'
return arg;
}

如何操作使得他能拥有这个size类型呢?其实很简单,只要你自己定义一个接口,让T去继承即可

1
2
3
4
5
6
7
interface Sizeable{
size:number
}
function trace<T extends Sizeable>(args:T): T{
console.log(args.size);
return args
}

泛型工具类型

为了方便开发者,ts设置了一些工具类比如Partial、Required、Readonly等。但我们需要知道一些基础知识方便我们后续用这些工具类

typeof

关于typeof我们知道是用于判断类型的,但你可能不会做下面这样的事情

1
2
3
4
5
6
interface Person{
name: string,
age: number
}
const jojo: Person = {name:'jojo',age:18};
type JOJO = typeof jojo;//type JOJO = Person

在上述代码用了typeof获取了变量的类型并赋给了JOJO,后面我们就可以用到JOJO类型

除此之外,typeof还可以用来获取函数的类型、对象的结构类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const Message = {
name: "jimmy",
age: 18,
address: {
province: '四川',
city: '成都'
}
}
type message = typeof Message;
/*
type message = {
name: string;
age: number;
address: {
province: string;
city: string;
};
}
*/
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]

keyof

该方法返回类型的所有键,返回值是联合类型

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
}

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number

keyof的作用

js是一门高度动态的语言,所以有些时候在我们的ts里面想要捕获到某些操作可能很麻烦,比如

1
2
3
4
function prop(obj:object,key:string){
return obj[key];
}
//Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.

元素隐式的拥有any类型,所以string类型不能用于索引{}类型,因此我们需要利用到keyof和泛型来帮助我们解决这个问题

1
2
3
function prop<T extend object,K extends keyof T>(obj: T,key: K){
return obj[key];
}

在上述的代码中,T约束了object,K约束了T的所有键,所以非常的合理,此时访问不存在的属性的时候,也会提示错误了。

in(具体场景?)

in用来遍历枚举类型

1
2
3
4
5
type Keys = "a" | "b" | "c"

type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }

infer

infer就是取到函数值的返回类型,方便后续使用

1
2
3
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;

extends

添加约束,上面已经讲到了用法和场景,略

索引类型

在实际开发中,我们经常需要在对象中获取一些值然后建立对应的集合

1
2
3
4
5
6
7
8
9
10
11
let person = {
name: 'musion',
age: 35
}

function getValues(person: any, keys: string[]) {
return keys.map(key => person[key])
}

console.log(getValues(person, ['name', 'age'])) // ['musion', 35]
console.log(getValues(person, ['gender'])) // [undefined]

但此时ts并没有对不存在的值进行报错,通过这里我们想到了keyof+extends,结合我们的索引类型操作符[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getValues<T, K extends keyof T>(person: T, keys: K[]): T[K][] {
return keys.map(key => person[key]);
}

interface Person {
name: string;
age: number;
}

const person: Person = {
name: 'musion',
age: 35
}

getValues(person, ['name']) // ['musion']
getValues(person, ['gender']) // 报错:
// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type "gender" is not assignable to type "name" | "age".

上述代码中,T[K]表示对象T的属性K所表示的类型,那么T[K][]就是对象T的属性K所表示类型的数组。

映射类型

根据旧的类型创造出新的类型,称之为映射类型

映射类型的用法非常巧妙,如下例子将接口的所有属性变成可选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface TestInterface{
name:string,
age:number
}
//希望将接口的所有属性变成可选
//在ts中可以通过+/-添加或者删除
type OptionTestInterface<T> = {
[p in keyof T]+?:T[p]
}
type newTestInterface = OptionTestInterface<TestInterface>
// type newTestInterface = {
// name?:string,
// age?:number
// }

将接口的所有属性变成只读

1
2
3
4
5
6
7
8
9
10
11

type OptionalTestInterface<T> = {
+readonly [p in keyof T]+?:T[p]
}

type newTestInterface = OptionalTestInterface<TestInterface>
// type newTestInterface = {
// readonly name?:string,
// readonly age?:number
// }

由于生成只读和可选属性比较常用,ts已经内置了Readonly和Partial工具类型

Partial 可选

我们看例子

1
2
3
4
5
6
7
8
9
10
11
interface UserInfo{
id: string,
name: string
}
//变成可选
type newUserInfo = Partial<UserInfo>;
//interface newUserInfo {
//    id?: string;
//    name?: string;
//}

Partial有局限性,他只负责第一层的数据,如果多层就要自己实现了。

DeepPartial 深度可选

1
2
3
4
5
6
7
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object
? DeepPartial<T[U]>
: T[U]
}
type PartialedWindow = DeepPartial<T>;
//所有属性都变成可选了

Required 必选

将所有属性变成必选

1
2
3
4
type Required<T> = { 
    [P in keyof T]-?: T[P] 
};

其中-?是为了移除可选

Readonly 只读

只读,不可修改

原理

1
2
3
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

eg

1
2
3
4
5
6
7
8
9
interface Todo {
title: string;
}

const todo: Readonly<Todo> = {
title: "Delete inactive users"
};

todo.title = "Hello"; // Error: cannot reassign a readonly property

Pick 挑选属性

原理

1
2
3
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

例子

1
2
3
4
5
6
7
8
9
10
11
12
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;
//TodoPreview 只有title和completed了
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};

Record

将K中所有属性值转换为T类型

原理

1
2
3
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

例子

1
2
3
4
5
6
7
8
9
10
11
interface PageInfo {
title: string;
}

type Page = "home" | "about" | "contact";

const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" },
};

ReturnType 获取返回类型

得到函数的返回值类型

原理

1
2
3
4
5
type ReturnType<T extends (...args: any[]) => any> = T extends (
  ...args: any[]
) => infer R
  ? R
  : any;

例子

1
2
type Func = (value: number) => string;
const foo: ReturnType<Func> = "1";

ReturnType获取到Func的类型是字符串,所以foo也只能被赋为字符串了

Exclude 移除

将某个类型中属于另外一个的类型移除,类似于类型去重

原理

1
type Exclude<T, U> = T extends U ? never : T;

例子

1
2
3
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

Extract 提取

从T中提取U,类似于找到唯一值

原理

1
type Extract<T, U> = T extends U ? T : never;

例子

1
2
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void

Omit 排他

Omit类似于排他思想 除了自己之外的都能用

原理

1
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

例子

1
2
3
4
5
6
7
8
9
10
11
12
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Omit<Todo, "description">;

const todo: TodoPreview = {
title: "Clean room",
completed: false,
};

NonNullable 排null&undefined

NonNullable用于过滤null和undefined

原理

1
type NonNullable<T> = T extendsnull | undefined ? never : T;

例子

1
2
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]

Parameters

用于获取参数类型组成的元组类型

原理

1
2
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any
? P : never;

例子

1
2
3
4
type A = Parameters<() =>void>; // []
type B = Parameters<typeofArray.isArray>; // [any]
type C = Parameters<typeofparseInt>; // [string, (number | undefined)?]
type D = Parameters<typeofMath.max>; // number[]

tsconfig.json

他是ts的重要配置文件,相当于vue.config.js,在这里讲几个重要的配置

files -设置要编译的文件名字
include -设置需要进行编译的文件,支持路径模式匹配
exclude -设置无需编译的文件,支持路径模式匹配
compilerOptions -设置与编译流程相关的选项

compilerOptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
"compilerOptions": {

/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。

/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}