本文内容来源于网络,个人归纳整理,只为理解记忆方便
参考文档:
- 《TypeScript 入门教程》
- typescript handbook
- 深入理解 TypeScript
- TypeScript 精通指南
- 【THE LAST TIME】Typescript 进阶 之 重难点梳理
- 细数 TS 中那些奇怪的符号
- TypeScript 中文手册
- type-challenges
1,一些概念
- 1,强类型语言和弱类型语言 强类型语言不允许任意的隐式类型转换。弱类型语言则相反。
- 2,静态类型语言和动态类型语言 静态类型语言,在变量声明过后,它的类型就不允许再修改。
1,一些特殊的运算符
1,可选链运算符 ?.
typescript 3.7实现。
const val = a?.b;
//编译的ES5代码
var val = a === null || a === void 0 ? void 0 : a.b;
2,空值合并运算符 ??
typescript 3.7实现。
当左侧操作数为null或者undefined时,其返回右侧的操作数,否则返回左侧的操作数。
const foo = null ?? 'default string';
console.log(foo); // 输出:"default string"
const baz = 0 ?? 42;
console.log(baz); // 输出:0
与逻辑或运算符不同,逻辑或会在其左侧操作数为falsy值('', NaN, 0)时返回右侧操作数。
空值运算符不能直接与与(&&)和或(||)操作符组合使用。
// '||' and '??' operations cannot be mixed without parentheses.(5076)
null || undefined ?? "foo"; // raises a SyntaxError
// '&&' and '??' operations cannot be mixed without parentheses.(5076)
true && undefined ?? "foo"; // raises a SyntaxError
3,可选属性
interface Person {
name: string;
age?: number;
}
let lolo: Person = {
name: "lolo"
}
4,& 运算符
通过&运算符可以将现有的多种类型叠加到一起成为一种类型,包含了所需类型的的所有特性。
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
let point: Point = {
x: 1,
y: 1
}
5,使用Typescript的好处
- 1,帮
- 2,结合编辑器,提供只能提示,提升开发体验。
- 3,服务端程序员更好理解,方便交流。
2,区分类型空间(type space)和值空间(value space)
参考文档:
不同于使用动态类型的JavaScript,Typescript等现代静态类型语言,一般都具有两个放置语言实体的空间,即类型空间和值空间。前者用于存放代码中的类型信息,在运行时会被完全擦除。而后者存放了代码中的值,会保留到运行时。
- TS中的一个符号可以属于类型空间或者值空间,也可以同时属于类型空间和值空间。
- class和enum同时属于类型空间和值空间。class实际上是两类对象的合体,一类作为构造函数及其原型,一类是类对象本身。
class Cylinder {
radius = 1;
height = 1
}
//等号左边的是实例的类型,右边的是构造函数。
const instance: Cylinder = new Cylinder();
- 有很多东西在两个空间下有不同的含义。
const在值空间修饰变量时表示变量不能重新赋值,而as const表示修改字面量的类型推导。
extends可以用来定义继承关系(class A extends B)或者定义子类型(interface A extends B)或者定义泛型约束(Generic<T extends number>).
1,类型空间
能用来作为类型标注。
- type
- interface
- class
- enum
其他
1,循环引用
// editor.ts
import { Element } from './element'
// Editor 中需要建立 Element 实例
class Editor {
constructor() {
this.element = new Element();
}
}
// element.ts
import { Editor } from './editor'
// Element 中需要标注类型为 Editor 的属性
class Element {
editor: Editor
}
//使用ts3.8的特性import type改造
// element.ts
import type { Editor } from './editor'
// 这个 type 可以放心地用作类型标注,不造成循环引用
class Element {
editor: Element
}
// 但这里就不能这么写了,会报错
const editor = new Editor()
2, 值空间
- 字面量
- 变量
- 常量
- 函数形参
- 函数对象
- class
- enum
3,鸭子类型
TS的类型是结构化类型,也称为鸭子类型。
当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以称为鸭子。
TS的类型系统是结构类型系统,任两个以相同结构描述的值的类型都是等价的。与此相反的是,表明类型系统,表示类型若要相等,就必须具有相同的名字。
2,基础
1,原始数据类型和对象类型
在JS中定义了一个变量为某个类型,后面还可以将它赋值为其他类型,但是在TS中,这样做会报错
- 1,原始数据类型:boolean, number, string, null, undefined, Symbol, BigInt
- 2, 对象类型
1,boolean
需要注意Boolean是包装对象,可以通过
这个类型只有两个实例:true,false
//创造的是对象不是布尔值
new Boolean(1)
//返回boolean类型
Boolean(1)
2,number
let decLiteral: number = 6;
//十六进制
let hexLiteral: number = 0xf00d;
//二进制
let binaryLiteral: number = 0b1010;
//八进制
let octalLiteral: number = 0o744;
//非数字
let notANumber: number = NaN;
//无穷大
let infinityLiteral: numer = Infinity;
八进制和二进制是ES6之后才引入的。
3,字符串
模板字符串是ES6之后引入。
4,空值 void
JavaScript没有空值的概念,在typescript中可以使用void表示没有任何返回值得函数。
function one(): void {
console.log("one")
}
但是声明一个void得变量没有什么作用,只能将这个变量赋值为null或者undefined。
void一般用于函数的返回值声明。
let one: void = null || undefined;
5, null 与 undefined
typescript现在有两种特殊的类型:null 和 undefined,他们的值分别为null和undefined。
默认情况下,null和undefined是所有类型的子类型,可以将他们赋值给其他类型。
6,字面量类型
在typescript中可以将字面量作为一种自定义的类型,
type Weekdays = 1 | 2 | 3 | 4 | 5;
let day: Weekdays = 1; // ok
day = 5; // ok
day = 6; // error: Type '6' is not assignable to type 'Weekdays'
字面量主要分为:真值字面量类型,数字字面量类型,枚举字面量类型,大整数字面量类型,和字符串字面量类型。
每个字面量类型都只有一个实例,所以他们也是单元类型。
7,never
never是其他类型的子类型,代表不会出现的值。声明为never类型的值,只能被never类型所赋值,
拥有void返回值类型的函数能正常运行。拥有never返回值类型的函数无法正常返回,无法终止,或者抛出异常。
某个变量的类型为never一般表示程序不会执行到这里。
let x: never;
let y: number;
// 运行错误,数字类型不能转为 never 类型
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();
// 运行正确,never 类型可以赋值给 数字类型
y = (()=>{ throw new Error('exception')})();
// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
throw new Error(message);
}
// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
}
8,object
object表示常规的JavaScript对象类型,非基础数据类型。
9,unknown
顶级类型,可以接收任何类型。
但无法赋值给其他类型,any和unknown本身除外。因此在需要接收所有类型的场景下,优先考虑用unknown代替any。
unknown类型是any类型的类型安全版本,当使用any类型时,应当先尝试使用unknown类型。
10,枚举类型
enum
11,symbol
12,对象类型
1, 包装(装箱)类型
String, Number, Boolean, Symbol, BigInt.
2,任意值
顶级类型。
any表示允许赋值为任意类型。
任何类型的值可以赋值给any类型。
any类型的值也可以赋值给每一种类型。
1,未声明类型的变量
变量在未声明类型的时候,会被识别为any类型。
3,类型推论
如果没有明确的指出变量的类型,没么typescript会根据类型推论的规则推断出一个类型。通常发生在初始化变量和成员,设置默认参数和决定参数返回值时。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断为any类型而完全不被类型检查。
4,联合类型
联合类型表示取值可以为多种类型中的一种。
1,访问联合类型的属性和方法
当typescript不确定联合类型的变量是哪一种类型的时候,我们只能访问联合类型的所有类型中共有的属性和方法。
5,接口
1,可选属性
2,任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它类型的子集。
interface Person {
name: string;
age?: number;
//使用了联合类型
[propName: string]: string | number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
3,只读属性
当我们在属性名前面加上readonly时,该属性从定义赋值之后就不能再修改。
4,函数属性(函数类型接口)
除了描述带有属性的普通对象外,接口也可以描述函数类型。
对方法传入的参数和返回值进行约束。
interface CompareFunc {
(first:number, second: number): number;
}
let numCompare: CompareFunc = function(first: number, second: number) {
return first > second ? first : second;
}
// success
numCompare: CompareFunc = function(one: number, two: number) {
return one > two ? one : two;
}
// success
numCompare: CompareFunc = function(first, second) {
return first > second ? first : second;
}
// success
5,可索引类型接口
一般用来约束数组和对象。
// 数字索引——约束数组
// index 是随便取的名字,可以任意取名
// 只要 index 的类型是 number,那么值的类型必须是 string
interface StringArray {
// key 的类型为 number ,一般都代表是数组
// 限制 value 的类型为 string
[index:number]:string
}
let arr:StringArray = ['aaa','bbb'];
console.log(arr);
// 字符串索引——约束对象
// 只要 index 的类型是 string,那么值的类型必须是 string
interface StringObject {
// key 的类型为 string ,一般都代表是对象
// 限制 value 的类型为 string
[index:string]:string
}
let obj:StringObject = {name:'ccc'};
6,
6,函数
1,函数定义
- 函数声明
输入多余的或者少于定义的参数,都是不被允许的。
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2, 3);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
return x + y;
}
sum(1);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
- 函数表达式
2,函数参数
- 1,可选参数
可选参数后面不允许再出现必需参数。
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
- 2,参数默认值
typescript会将添加了默认值的参数识别为可选参数,但是此时不受可选参数后面不允许出现必需参数的限制,不想传入参数是可以传入undefined,不能不传入参数。
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
- 3,剩余参数 剩余参数之后不能再有其他参数(即剩余参数只能是最后一个参数),否则会报错。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a: any[] = [];
push(a, 1, 2, 3);
3,函数重载
7,类型断言
当你比typescript更了解某个变量值的详细信息,可以通过类型断言来覆盖它的类型判断。
类型断言可以用来手动指定一个值得类型,即允许变量从一种类型变更为另一种类型。
语法格式:
<类型>值
或者
值 as 类型
let value: any = 'hello world';
let len = (<string>value).length;
//or
let len = (value as string).length;
1,类型断言的作用
- 1,类型断言可用用来手动指定一个值的类型。
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
类型断言只能typescript编译器,无法避免运行时的错误。
- 2,将一个父类断言为更加具体的子类
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
- 3,将任何一个类型断言为any typescript的类型系统运行良好,每个值得类型都具体而精确,当我们引用一个在此类型上不存在的属性或者方法时,就会报错。
const foo: number = 1;
foo.length = 1;
// index.ts:2:5 - error TS2339: Property 'length' does not exist on type 'number'.
但是在any类型的变量上,访问任何属性都是允许的。
(window as any).foo = 1;
将一个变量断言为any类型,是解决typescript类型问题的最后一个手段。
2,类型断言的限制
8,type
类可以使用相同的形式去实现interface和type,但是不能实现或者继承联合类型的type。
把类型当做值的集合思考。
type A= 'A' // 单值集合 { 'A' }
type B= 'B' // 单值集合 { 'B' }
type AB = 'A' | 'B' // 集合的并集 { 'A', 'B' }
type twoInt = 2 | 4 | 5 ... // 无限元素集合 { 1,2,3,4}
type threeInt = 3 | 6 | 9 // 无限集合
type twoIntersectThreeInt = twoInt & threeInt // 无限集合的交集
type twoUnionThreeInt = 2| 3 | 4 | 6 ... // 无限集合的并集
keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)
1,type和interface的区别
- 声明合并 和type不同,interface可以重复定义,并且会被自动聚合。
interface Point {x:number;};
interface Point {y:number;};
const point:Pint = {x:1,y:2};
9,类型运算
1,集合运算:&和|操作符
在这里&和|不是位运算符,&表示必须满足所有的契约,|表示可以只满足一个契约。
使用&得到的是交叉类型。
使用|得到的是合集类型。
interface IA{ a:string; b:string;}
type TB{ b:number; c:number [];}
type TC = TA | TB;// TC 的 key,包含 ab 或者 bc 即可,当然,包含 bac 也可以type
TD = TA & TB;// TD 的 可以,必须包含 abc
2,索引签名
10,运算符
- keyof keyof用来获取一个类型的所有键值,与Object.keys类似,前者获取interface的键,后者获取对象的键。
interface Person {
name: 'jack Ma',
age: 17,
}
type T1 = keyof Person;
// 'name' | 'age'
- in in 通常用来实现枚举类型遍历
type Keys = 'name' | 'age';
type Person = {
[K in Keys]: any;
}
// { name: any, age: any }
1,算术运算符
2,关系运算符
3,逻辑运算符
4,位运算符
5,赋值运算符
6,三元运算符
Test ? expr1 : expr2
7,类型运算符
1、typeof 运算符
1,keyof
这个运算符专门在类型空间中使用。
keyof返回的是一个集合。
2,extends
在类型空间中的语义并非class和interface中的继承,类似于由一个类型表达式来衍生出另一个类型变量。
11, 类型注解
typescript通过类型注解提供编译时的静态类型检查,可以在编译阶段发现潜在bug,同时编码过程提示更加智能化,类似于强类型语言的类型声明。
12,交叉类型(&)
交叉类型是将多个类型合并为一个类型,可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
interface Person {
name: string,
age: number,
}
interface Animal {
walk: boolean,
}
let jack: Person & Animal = {
name: 'jack',
age: 17,
walk: true,
}
// success
let jack: Person & Animal = {
name: 'jack',
age: 17,
}
// error
let jack: Person & Animal = {
name: 'jack',
age: 17,
walk: true,
eat: true,
}
// error
13,合集类型
Union Type.
合集类型的构成元素既可以是类型,也可以是字面量。
14,字面量类型(literal types)
值除了可以作为类型的值,还可以作为类型。在类型位置时,表示的是类型;写在值位置时,表示的就是类型值。通常和类型别名联合使用。
const p3: { name: string, age: number } = {
name: 'TypeScript',
age: 5
};
const git: 'git' = 'git';
const bookCount: 10 = 10;
const spring: Season.Spring = Season.Spring;
如上,有字面量种类:
- 基本数据类型
- 对象
15,兼容类型&子类型
编程语言的子类型,分为两种:
- 名义子类型 Java和c#中就是名义子类型,必须显式继承,用来extends才是子类型。
- 结构子类型 只要结构相同,就是子类型。typescript是结构子类型
类型兼容(子类型)判断,可以用子集来描述:
从集合角度来看,A是B的子集;从类型角度看,A是父类,B是子类。
3,进阶
1,类型别名
类型别名用来给一个类型起个新名字。
别名类型不像interface,class,字面量类型那样,它不是新建的类型,新建的是名称。
type StrType = string;
let str:StrType = 'hello world';
2,元祖
数组合并了相同类型的对象,但元祖(Tuple)合并了不同类型的对象。
- 可以只赋值其中一项
let tom: [string, number];
tom[0] = 'Tom';
- 但是当对元祖类型的变量进行初始化或者赋值的时候,需要提供所有元祖类型中指定的项。
let tom: [string, number];
tom = ['Tom', 25];
let tom: [string, number];
tom = ['Tom'];
// Property '1' is missing in type '[string]' but required in type '[string, number]'.
- 当添加越界的元素时,它的类型被限制为元祖中每个类型的联合类型。
let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.
- 元祖越界问题
let aaa: [string, number] = ['aaa', 5];
// 添加时不会报错
aaa.push(6);
// 打印整个元祖不会报错
console.log(aaa); // ['aaa',5,6];
// 打印添加的元素时会报错
console.log(aaa[2]); // error
TS4.0新增的元祖类型声明方式
1,可变元祖类型
type Foo<T extends any[]> = [boolean, ...T, boolean]
1,命名元祖类型
//普通元祖
const Address = [string, number]
function setAddress(...args: Address) {
// some code here
}
//命名元祖
const Address = [streetName: string, streetNumber: number]
function setAddress(...args: Address) {
// some code here
}
普通元祖只能提示参数的类型,使用命名元祖之后可以提示参数的含义。只要目的是使得编码过程中的提示更加智能。
3,枚举
使用枚举可以定义一些带名字的常量。 使用枚举类型可以为一组数值赋予友好的名字。
- 常数项和计算所得项 枚举项有两种类型:常数项和计算所得项。
enum Color {Red, Green, Blue = "blue".length};
如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错
enum Color {Red = "red".length, Green, Blue};
// index.ts(1,33): error TS1061: Enum member must have initializer.
// index.ts(1,40): error TS1061: Enum member must have initializer.
- 常数枚举 常数枚举是使用 const enum 定义的枚举类型。
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
常数枚举和普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。 加入包含了计算成员,则会在编译阶段报错。
1,枚举的使用场景
function initByRole(role) {
if (role === 1 || role == 2) {
console.log("1,2")
} else if (role == 3 || role == 4) {
console.log('3,4')
} else if (role === 5) {
console.log('5')
} else {
console.log('')
}
}
//使用枚举
enum Role {
Reporter,
Developer,
Maintainer,
Owner,
Guest
}
function init(role: number) {
switch (role) {
case Role.Reporter:
console.log("Reporter:1");
break;
case Role.Developer:
console.log("Developer:2");
break;
case Role.Maintainer:
console.log("Maintainer:3");
break;
case Role.Owner:
console.log("Owner:4");
break;
default:
console.log("Guest:5");
break;
}
}
init(Role.Developer);
4,类
1,实例属性
在ES6中只能在construct()方法中使用 this.xxx定义实例属性,但是在ES7中可以在类中直接定义。
class Animal {
//ES7的用法
name = 'Jack';
//ES6的用法
name: string;
constructor() {
this.name = 'Jack'
}
}
类的简写形式:
//用构造函数的参数直接定义属性。
class Animal {
constructor(public name: string) {
this.name = 'Jack'
}
}
2, 静态属性
在ES7中提出了静态属性的概念,可以使用static定义一个静态属性:
class Animal {
static num = 42;
constructor() {
// ...
}
}
console.log(Animal.num); // 42
3,修饰符
typescript中可以使用三种修饰符:private,protected,public。
private修饰符:
当构造函数的修饰符为private时,该类不允许被继承或者实例化。
class Animal {
public name;
private constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal('Jack');
protectd修饰符:
当构造函数的修饰符为protected时,该类只能被继承。
class Animal {
public name;
protected constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal('Jack');
4,只读属性关键字(readonly)
只读属性关键字,只允许出现在属性声明、索引签名或者构造函数中。
//属性声明
class Animal {
readonly name;
public constructor(name) {
this.name = name;
}
}
5,抽象类
abstract用于定义抽象类和其中的抽象方法。
抽象类不允许被实例化。
抽象类中的抽象方法必须被子类实现。
6,继承
派生类包含了一个构造函数,必须调用super(),它会执行基类的构造函数。在构造函数中访问this属性前,必须要调用super().
5,接口
接口可以用于对对象的形状进行描述,还可以对类的一部分进行抽象。
接口只提供类型检查,不提供具体实现。
1,类实现接口(类类型接口)
如果接口
interface Alarm {
alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类可以实现多个接口。
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
- 2,接口继承类
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
为什么typescript会支持接口继承类?
在声明 class Point的时候,除了会创建一个名为Point的类之外,还会创建一个名为Point的类型。所以既可以将Point当做一个类来使用,也可以当做一个类型来使用。
6,泛型
主流的编程语言通常都支持泛型以提供更加出色的抽象能力。
泛型是指在定义函数,接口或者类的时候,不预先指定具体的类型,而是在使用的时候在指定类型。
定义泛型的时候可以一次定义多个类型参数。
泛型是强类型语言的一个比较重要的概念,允许我们在编码的时候暂时不指定类型,使用一些以后才指定的类型,在实例化时作为参数指明这些类型,合理的使用泛型可以提升代码的复用性,让系统更加灵活。
本质上,泛型可以理解为一个类型层面的函数,当我们指定具体的输入类型时,得到的结果是经过处理后的输出类型。
1,泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或者方法。
这时可以对泛型进行约束,只允许传入包含length属性的变量,
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity(7);
// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
2,泛型推断
表示将要推断的类型变量。
type InferType<T> = T extends (infer R)[] ? R : T;
type T1 = InferType<string[]>;
// string
type T2 = InferType<number>;
// number
#### 2,工具泛型
工具泛型是一些泛型的语法糖实现,也可以由自己手写。
7,泛型工具类型
几个比较重要的关键字:
- keyof keyof用于获取某种类型的所有键,其返回类型是联合类型。
// keyof 用于获取某种类型的所有键,其返回类型是联合类型
interface B {
id: number;
name: string;
age: number;
}
type B1 = keyof B;
// type B1 = "id" | "name" | "age"
- extends
extends关键字存在多种用途,在interface中表示类型拓展,在条件类型语句中表示布尔运算,在泛型中起到限制的作用,在class中表示继承。
// 表示类型扩展
interface A {
a: string
}
interface B extends A { // { a: string, b: string }
b: string
}
// 条件类型中起到布尔运算的功能
type Bar<T> = T extends string ? 'string' : never
type C = Bar<number> // never
type D = Bar<string> // string
type E = Bar<'fooo'> // string
// 起到类型限制的作用
type Foo<T extends object> = T
type F = Foo<number> // 类型“number”不满足约束“object”。
type G = Foo<string> // 类型“string”不满足约束“object”。
type H = Foo<{}> // OK
// 类继承
class I {}
class J extends I {}
使得 a extends b在布尔运算或者泛型中成立的条件是 a 是 b 的子集,a 需要比 b 更具体。
type K = '1' extends '1' | '2' ? 'true' : 'false' // "true"
type L = '1' | '2' extends '1' ? 'true' : 'false' // "false"
type M = { a: 1 } extends { a: 1, b: 1 } ? 'true' : 'false' // "false"
type N = { a: 1, b: 1 } extends { a: 1 } ? 'true' : 'false' // "true"
下面的extends是用来限制类型的。
type T = {
id: number;
name: string;
}
type K = {
id: number;
}
type IType = K extends T ? K : T;
// type IType = {
// id: number;
// name: string;
// }
// 此处 K extends T 限制K中必须有T的所有属性, 通俗点说就是T必须是K的子集
// 联合类型extends
type T = "id" | "name";
type K = "id";
type IType = K extends T ? K : T;
// type IType = "id"
// 此处限制为K必须包含于T,通俗点说就是K是T的子集
1,Partial
- 定义
type Partial<T> = {
[P in keyof T]?: T[P];
};
- 用法
/**
* Make all properties in T optional
*/
type Person = {
name: string;
age: number;
}
// 直接使用初始化所有参数都是必填
let tom:Person = {
name: 'tom',
age: 20
};
// 使用Partial将Person中所有的参数变为非必填
type PartialPerson = Partial<Person>;
let partialPerson: PartialPerson = {
name: 'tom'
};
//特殊情况:内部的对象的参数不能被处理为非必填
type Person = {
name: string;
age: number;
contact: {
email: string;
phone: number;
wechat: string;
}
}
type PartialPerson = Partial<Person>;
// 可以看出来 第一层属性都变成了非必填 第二层都没变
let partialPerson: PartialPerson = {
name: 'tom',
contact: { // error
email: 'goodboyb@qq.com'
}
};
//可以新定义一个工具类型
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Object ? DeepPartial<T[P]> : T[P];
}
2,Required
- 定义
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
- 用法
interface User {
id: number;
age: number;
}
type PartialUser = Partial<User>;
// type PartialUser = {
// id?: number;
// age?: number;
// }
type PickUser = Required<PartialUser>;
// type PickUser = {
// id: number;
// age: number;
// }
3,Record
- 定义
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
- 用法
type petsGroup = 'dog' | 'cat' | 'fish';
type numOrStr = number | string;
type IPets = Record<petsGroup, numOrStr>;
// type IPets = {
// dog: numOrStr;
// cat: numOrStr;
// fish: numOrStr;
// }
4, Pick
- 定义
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
- 用法
interface B {
id: number;
name: string;
age: number;
}
type PickB = Pick<B, "id" | "name">;
// type PickB = {
// id: number;
// name: string;
// }
5,Exclude
- 定义
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
- 用法
// 例子1
type T = {
name: string
age: number
}
type U = {
name: string
}
type IType = Exclude<keyof T, keyof U>
// type IType = "age"
type T0 = Exclude<"a" | "b" | "c", "a" | "b">
// type T0 = "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b" | 's'>
// type T1 = "c"
6,Extract
Extract意思是提取,Extract<T, U>从T中提取那些可分配给U的类型,可以理解为取交集。
- 定义
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
- 用法
type T0 = Extract<"a" | "b" | "c", "a" | "f">
// type T0 = "a"
type T = {
name: string
age: number
}
type U = {
name: string
}
type IType = Extract<keyof T, keyof U>
// type IType = "name"
8,tsconfig.json文件
tsconfig.json作为typescript的配置文件,当一个目录中存在tsconfig.json文件,可以认为该目录为typescript项目的根目录。
通常tsconfig.json文件主要包含两部分内容:指定待编译文件和定义编译选项。
1,创建tsconfig.json文件的方式
1,手动项目根目录创建
2,通过 tsc --init 初始化tsconfig.json文件
3,也可以使用tsc命令来编译ts源文件
/*
参数介绍:
--outFile // 编译后生成的文件名称
--target // 指定ECMAScript目标版本
--module // 指定生成哪个模块系统代码
index.ts // 源文件
*/
$ tsc --outFile leo.js --target es3 --module amd index.ts
在不指定输入文件的情况下执行tsc命令(需要在项目的根目录中存在tsconfig.json文件,如果不存在这个文件,会报错),默认从当前目录开始编译,编译所有ts文件(tsconfig.json作为项目配置文件的项目下的所有TS文件都会被编译),并且从当前目录开始查找tsconfig.json文件,并逐级向上级目录搜索。
tsc
tsc的其他编译选项
也可以为tsc命令指定参数--project或者-p来明确需要编译的目录,该目录需要包含一个tsconfig.json文件。
2,tsconfig.json的文件结构
1,按照顶层属性分类
1, compileOnSave
设置保存文件时自动编译,但需要编译器支持,类型boolean,默认值为false。
{
// ...
"compileOnSave": false,
}
2, compilerOptions
配置编译选项,若compilerOptions属性没有填写具体值,编译器会使用默认值。
- target
- outFile
- sourceMap
{
// ...
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量,如果有未赋值的变量,或者试图把null和undefined赋值给不允许为空的变量,类型检查器就会抛出一个错误。
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
}
3, exclude
指定编译器需要排除的文件或者文件夹。默认排除node_modules文件夹文件。
值为字符串数组。
支持glob通配符。
glob是shell使用的路径匹配符,类似于正则表达式,但是与正则表达式不完全相同。其基本语法如下:
.,匹配一个路径中0个或者多个字符,注意:不匹配以.开始的路径,例如文件.a.- ? ,匹配一个字符。
- [...], 匹配一系列字符,例如:[abc]匹配字符a, b, c,在[^...]和[!...]表示匹配不在列表中的字符。
- **, 匹配0个或者多个子文件夹。
- {a,b}, 匹配a或者b,a和b也可以是通配符。
- ! :排除文件,例如:!a.js表示排除文件a.js。
{
// ...
"exclude": [
"src/lib" // 排除src目录下的lib文件夹下的文件不会编译
]
}
4, extends
引入其他配置文件,继承配置。
{
// ...
// 把基础配置抽离成tsconfig.base.json文件,然后引入
"extends": "./tsconfig.base.json"
}
5, files
指定需要编译的单个文件列表,默认包含当前文件夹和子目录下的所有typescript文件。
{
// ...
"files": [
// 指定编译文件是src目录下的leo.ts文件
"scr/leo.ts"
]
}
6, include
指定需要编译的文件或者目录
{
// ...
"include": [
// "scr" // 会编译src目录下的所有文件,包括子目录
// "scr/*" // 只会编译scr一级目录下的文件
"scr/*/*" // 只会编译scr二级目录下的文件
]
}
7, references
指定工程引用依赖。
在项目开发中,有时候我们为了方便将前端项目和后端node项目放在同一个目录下开发,两个项目依赖同一个配置文件和通用文件,但我们希望前后端项目进行灵活的分别打包,那么我们可以进行如下配置:
{
// ...
"references": [ // 指定依赖的工程
{"path": "./common"}
]
}
8, typeAcquisition
typeAcquisition 属性作用是设置自动引入库类型定义文件(.d.ts)相关。
包含 3 个子属性:
enable : 布尔类型,是否开启自动引入库类型定义文件(.d.ts),默认为 false;
include : 数组类型,允许自动引入的库名,如:[“jquery”, “lodash”];
exculde : 数组类型,排除的库名。
- enable
- include
{
// ...
"typeAcquisition": {
"enable": false,
"exclude": ["jquery"],
"include": ["jest"]
}
}
9,oop
面对对象编程:oop应该体现一种网状结构,这个结构上的每个节点(Object)只能通过消息和其他节点通信。每个节点会有内部隐藏的状态,状态不可以直接被修改,而应该通过消息传递的方式来间接的修改。
我们在OOP时,很多时候都是在处理编码的细节工作,而非oop提倡的独立和通信。
对class来说,实际上我们对它的用法有:
- 表达一个类型,以对应真是世界的概念。一个类型可以起到一个模板的作用。这个类型生成的对象会严格维护内部的状态(或者叫不变量)
- 表达一个Object(即单例),比如XXXService这种Bean。
- 表达一个名字空间,把一组相关的代码写到一起,类似于一个module。
- 表达一个数据结构,例如DTO.
- 代码复用
- 提供便利,使得
foo(a)可以写为a.foo(a)。
10,typescript声明文件
菜鸟教程的这个解释不错: TypeScript 声明文件
typescript作为JavaScript的超集,在开发过程中不可避免需要引入其他第三方的JavaScript的库。虽然通过直接调用可以调用库的类和 方法,但是却无法使用typescript诸如类型检查等特性功能。
通过引用这个声明文件,就可以借用typescript的各种特性来使用库文件。
//declare var并没有真的定义一个变量,只是定义了全局变量$的类型,
//仅仅会用于编译时的检查,在编译结果中会被删除。
declare var $: (selector: string) => any;
//在ts中使用jQuery
$('body')
11,tsconfig.json文件配置
1, 配置项
1,compilerOptions
编译选项。
1,基础配置
2,严格类型检查配置
3,附加检查
4,模块解析配置
5,source map 配置
6,实验性配置
2, include
[ ].
指定要编译的路径列表,但是和files的区别在于:这里的路径可以使文件夹,也可以是文件,可以使用相对和绝对路径,可以使用通配符。
3,exclude
[ ].
exclude表示要排除的,不编译的文件,可以指定一个列表,规则和include一样
4,extends
extends通过指定一个其他的tsconfig.json的文件路径,来继承这个配置文件中的配置,继承来的文件会覆盖当前文件定义的配置。ts3.2之后,支持继承一个来自Node.js包的tsconfig.json配置文件。
5,files
[ ].
配置一个列表,里面指定文件的相对路径或绝对路径,编辑器在编译时只会编译在files中列出的文件,优先级高于include。如果files不指定,就取决于是否设置include选项,如果include也没有配置,则默认会编译根目录以及所有子目录中的文件。指定的项必须是文件,而不能是文件夹,不能使用通配符。
6,compileOnSave
boolean.
7, references
array.
4,typescript演进过程
2021年7月1日16:17:28 :typescript最新版本4.3.4