TS产生的原因

​ Js没有面向对象的设计理念,不适合开发大型项目。Ts是js的超集,包含了js的所有元素,同时还增加了静态类型、类、模块、接口和类型注解方面的功能。JS是一门弱类型语言,很多错误只有在运行时才会被发现。而TS提供了一套静态检测机制,可以在编译时就发现错误。

TS和JS的区别

  • 语言层面:JS和TS都是ECMAScript的体现

  • 执行环境层面:浏览器和node.js都能直接运行JS,但是不能直接运行TS

  • 时序层面:TS被真正执行前,会通过编译转换生成JS,之后才能被解释执行

  • 厂商层面:JS由Netscape率先推出,由各大浏览器厂商实现。TS由微软推出,由微软维护。

​ 总结:TS是JS的语法糖,JS程序可以直接移植到TS,TS需要编译成JS才能被浏览器执行。

TS数据类型

基本数据类型

​ boolean, object, string, symbol, bigint, undefined, null, number

null、undefined

​ 默认情况下,null和undefined是所有类型的子类型,也就是可以把null和undefined赋值给其他类型

引用数据类型–数组

  • 数组的两种定义方式
1
2
let arr: string[] = ["1", "2"];
let arr: Array<string> = ["1", "2"];
  • 联合类型数组定义
1
let arr: (number | string)[] = [1, "2"];
  • 定义指定对象成员的数组
1
2
3
4
5
interface Arrobj{
Name: string,
Age: number
}
let arr: Arrobj[] = [{name: "hi", age: 11}];

引用数据类型—函数

  • 函数声明
1
function sum(x: number, y:number): number {return x+y;}
  • 函数表达式
1
let mySum: (x: number, y: number) => number = function(x: number, y: number): number {return x+y;}
  • 使用接口定义函数类型
1
2
3
interface searchFunc{
(source: string, substring: string): boolean;
}
  • 剩余参数
1
2
3
4
5
function push(array: any[], …items: any[]){
items.forEach(function(item) { array.push(item); });
}
let a = [];
push(a, 1, 2, 3);
  • 函数重载/方法重载:使用相同名称和不同参数数量或类型创建多个方法的能力
1
2
3
4
5
6
7
8
9
10
11
12
13
type Types = number | string;
function add(a: number, b: number): number;
function add(a: string, b: number): string;
function add(a: string, b: string): string;
function add(a: string, b: string): string;
function add(a: Types, b: Types) {
if(typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
return a+b;
}
const result = add("a", "b");
result.split(" ");

引用数据类型—对象

​ 小object代表的是所有非原始类型,所以不能把number\boolean\string\symbol\bigint等原始数据类型赋值给object。在严格模式下,null和undefined类型也不能赋值给object。

​ 大Object代表所有拥有toString、hasOwnProperty方法的类型,所以所有原始类型、非原始类型都可以赋给Object。同样,在严格模式下,null和undefined类型也不能赋给Object。

​ {}与大Object一样。

元组

​ 元组的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。适合用于保存定长定数据类型的数据。

1
let x: [string, number]; // 对应位置类型必须匹配,number为两个

元组解构

1
2
let employee: [number, string] = [1, "semlinker"]; 
let [id, username] = employee;

其他数据类型–never

​ never表示的是那些永不存在的值的类型。值永不存在的两种情况:1.如果函数执行时抛异常,那么这个函数永远不存在返回值。2.函数中执行无限循环的代码,使得程序永远无法运行到函数返回值。

​ never可以赋值给任何类型,但是没有类型能赋值给never类型(除本身),即使any也不可以

​ 在ts中,可以利用never类型的特性来实现全面性检查:

1
2
3
4
5
6
7
8
9
10
11
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if(typeof foo === “string”) {

}else if(typeof foo === “number”){

}else{
// 若Foo类型变成string | number | boolean,那么下面的语句就会报错,也就是进行了类型收窄。所以使用never避免出现新增了联合类型没有对应的实现,写出了类型绝对安全的代码
const check: never = foo;
}
}

any

​ 变量在声明时未指定类型,则会被识别为any类型

unknown

​ unknown与any类似。它们的最大区别是:任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown任何类型都可以赋值给它,但它只能赋值给unknown和any.

类型断言语法

1
2
3
4
5
6
7
8
9
// 尖括号语法
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;

// 两种写法没有任何区别,但是尖括号格式会与reactjsx产生语法冲突
  • 类型断言–非空断言

​ 后缀表达式操作符!可以用于断言操作对象是非null和非undefined类型,例如:x!将从x值域中排除null和undefined.

1
2
3
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)
  • 类型断言–确定赋值断言

​ 允许在实例属性和变量声明后面放置!号,从而告诉ts该属性会被明确地赋值.

1
2
3
4
5
6
let x : number;
initialize()
console.log(2 * x); // error: variable ‘x’ is used before being assigned.
function initialize(){
x = 10;
}
1
2
3
4
5
6
let x! : number;
initialize()
console.log(2 * x); // ok.
function initialize(){
x = 10;
}

交叉类型

​ 交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含所需的所有类型的特性,使用&定义交叉类型。

​ 如果仅仅把原始类型、字面量类型、函数类型等原子类型合并成交叉类型,是没有用处的,因为没有类型能同时属于多种原子类型,比如既是string类型又是number类型。交叉类型可以将多个接口类型合并成一个类型,从而实现等同接口继承的效果。

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

任意属性

​ 除包含必选和可选属性之外,还允许有其他的任意属性,可以使用索引签名的形式来满足上述要求。一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集。

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: ‘Tom’,
gender: ‘male’
};

鸭式辨型法:通过某个规则来判定对象是否实现了这个接口

1
2
3
4
5
6
7
8
interface LabeledValue {
label: string;
}
function printLabel(labeledValue: LabeledValue) {
console.log(labeledValue.label);
}
let myObj = {size: 10, label: "size 10 object"};
printLabel(myObj); //ok
1
2
3
4
5
6
7
interface LabeledValue {
label: string;
}
function printLabel(labeledValue: LabeledValue) {
console.log(labeledValue.label);
}
printLabel({size: 10, label: "size 10 object"}); //error

​ 在参数里写对象就相当于直接给labeledObj赋值,这个对象有严格的类型定义,不能多参和少参。而当你在外面使用另一个变量myObj接收,myObj不会经过额外属性检查,但会根据类型推论为let myObj: {size: number; label: string} = {size: 10, label: “size 10 Object”};,然后将myObj再赋值给labeledObj,此时根据类型的兼容性,两种类型对象,参照鸭式辨型法,因为都具有label属性,所以被认定为两个相同,故可以绕开多余的类型检查。

绕开额外属性检查的方式

  • 类型断言 as
  • 索引签名
1
2
3
4
interface Props{
name: string;
[key: string]: any;
}

ts中接口与类型的区别

​ 大多数情况下使用接口类型和类型别名的效果等价。但是两者也存在很多不同。Ts的核心原则之一是对值所具有的结构进行类型检查。而接口的作用就是为这些类型命名和为代码或第三方代码定义数据模型。Type(类型别名)会给类型起个新名字,但是它不会创建新类型。

  • 两者都可以用于描述对象或函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// interface
interface Point{
x: number;
y: number;
}
interface setpoint{
(x: number, y: number): void;
}

// type alias
type Point = {
x: number;
y: number;
}
type setpoint = (x: number, y: number) => void;
  • 与接口不同,类型别名可以用于其他类型,如基本类型、联合类型、元组
1
2
3
4
5
6
7
8
9
10
11
12
// 基本类型
type Name = string;
// 对象
type PartialPointX = {x: number};
type PartialPointY = {y: number};
// 联合类型
type PartialUnion = PartialPointX | PartialPointY;
// 元组
type Data = [number, string];
// dom
let div = document.createElement(‘div’);
type B = typeof div;
  • 接口可以定义多次,会被自动合并为单个接口
1
2
3
interface Point {x: number;}
interface Point {y: number;}
const point: Point = {x: 1, y: 2};
  • 扩展方式不同

​ 两者的扩展方式不同,但并不互斥。接口可以扩展类型别名,类型别名也可以扩展接口。接口的扩展是通过继承,通过extends来实现。类型别名的扩展就是交叉类型,通过&来实现。

  1. 接口扩展接口
1
2
3
4
5
6
interface PointX {
x: number;
}
interface Point extends PointX {
y: number;
}
  1. 类型别名扩展类型别名
1
2
3
4
5
6
type PointX = {
x: number;
}
type Point = PointX & {
y: number;
}
  1. 接口扩展类型别名
1
2
3
4
5
6
type PointX = {
x: number;
}
interface Point extends PointX {
y: number;
}
  1. 类型别名扩展接口
1
2
3
4
5
6
interface PointX {
x: number;
}
type Point = PointX & {
y: number;
}

泛型

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

​ 如果想使用泛型上的属性,例如size,如果直接使用会报错。直观的想法是限定函数的参数类型应该有某属性,使用extends关键字可以做到这点。

1
2
3
4
5
6
7
// 例如要使参数具有size属性,可以如下
interface Sizeable {
size: number;
}
function trace<T extends Sizeable> (arg: T): T{
console.log(arg.size);
}
泛型工具类型
  • typeof:在类型上下文中获取变量或者属性的类型
1
2
3
4
5
6
interface Person {
name: string;
age: number;
}
const sem: Person = {name: “sss”, age: 30};
type Sem = typeof sem;
  • keyof:用于获取某种类型的所有键,其返回类型是联合类型
1
2
3
4
5
6
7
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

​ Ts中支持字符串索引和数字索引:为同时支持两种索引类型,就要求数字索引的返回值必须是字符串索引返回值的子类。因为当使用数字索引时,JS在执行索引操作时,会先把数值索引转换为字符串索引。

1
2
3
4
5
6
7
8
interface StringArr{
[index: string]: string;
}
interface StringArr1{
[index: number]: string;
}
type k1 = keyof StringArr; //string | number
type k2 = keyof StringArr1; // number

​ keyof也支持基础数据类型

1
2
3
let k1: keyof boolean; // “valueOf”
let k2: keyof number; // “toString” | “toFixed”…
let k3: keyof symbol; // “valueOf”

​ 经常遇到这个报错:Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type ‘{}’. 这是因为元素隐式拥有any类型,string类型不能被用于索引{}类型。

​ 解决方案有两个:一是暴力,这样失去了类型限制的意义。二是继承。

1
2
3
4
5
6
7
8
9
// 解决方案一:暴力
function prop(obj: object, key: string) {
return (obj as any)[key];
}

// 解决方案二:继承
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
  • in:用来遍历枚举类型
1
2
3
4
type keys = “a” | “b” | “c”;
type Obj = {
[p in keys]: any
}
  • infer:可以用于声明一个类型变量并对它进行使用。infer可以通过已知的类型和获得它泛型反推出泛型参数
1
2
3
4
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
// infer R就是声明一个变量来承载传入函数签名的返回值类型
1
2
type getIntersection<T> = T extends (a: infer P, b: infer P) => void ? P : never;
type Intersection = getIntersection<(a: string, b: number)> => void;
  • extends:可以进行泛型约束或继承
1
2
3
4
5
6
7
8
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
  • 索引:对象索引
  • 映射类型:根据旧类型创建新的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface TestInterface{
name:string,
age:number
}
// 我们可以通过+/-来指定添加还是删除

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

type newTestInterface = OptionalTestInterface<TestInterface>
// type newTestInterface = {
// name?:string,
// age?:number
// }
  • 内置工具类型—Partial:将类型的属性变成可选。但是Partial有个局限性,就是只支持处理第一层的属性,如果想处理多层属性需要使用DeepPartial
1
2
3
type Partial<T> = {
[P in keyof T]?: T[P];
};
  • DeepPartial
1
2
3
4
5
6
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]
// -?代表移除?这个modifier的标识
};
  • Readonly:将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值
  • Pick:从某个类型中挑出一些属性来
1
2
3
4
5
6
7
8
9
10
11
12
13
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
interface Todo{
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: 'clean room',
completed: false
}
  • Record<K extends keyof any, T>:将K中所有的属性的值转为T类型
1
2
3
4
5
6
7
8
9
10
11
12
typeof Record<K extends keyof any, T> = {
[P in K]: T;
}
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
6
7
8
9
// 定义
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: any;
// 例子
type Func = (value: number) => string;
const foo: ReturnType<Func> = "1";
  • Exclude<T, U>:将某个类型中属于另一个的类型移除掉
1
2
3
4
5
6
// 定义
type Exclude<T, U> = T extends U ? never : T;
// 例子
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>:从T中提取出U
1
2
3
4
5
// 定义
type Extract<T, U> = T extends U ? T : never;
// 例子
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
  • Omit<T, K extends keyof any>的作用是使用T类型中除了K类型的所有属性,来构造一个新的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 例子
interface Todo {
title: string;
description: string;
completed: boolean;
}

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

const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
  • NonNullable:用来过滤类型中的null及undefined
1
2
3
4
5
6
// 定义
type NonNullable<T> = T extends null | undefined ? never : T;

// 例子
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
  • Parameters:用于获得函数的参数类型组成的元组类型
1
2
3
4
5
6
7
8
9
// 定义
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any
? P : never;

//例子
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项目的配置文件,包含ts编译的相关配置,通过更改编译配置项,可以让ts编译出ES6、ES5、node的代码。

文件中重要字段

  • files: 设置要编译的文件的名称

  • include: 设置需要进行编译的文件,支持路径模式匹配

  • exclude:设置无需进行编译的文件,支持路径模式匹配

  • compilerOptions:设置与编译流程相关的选项