Skip to content

typescript

typescript是什么

  • Typescript是由微软开发的一款开源的编程语言
  • Typescript是Javascript的超集,遵循最新的ES5/ES6规范。TypeScript扩展了Javascript语法
  • TypeScript更像后端Java、C#这样的面向对象语言可以让JS开发大型企业应用
  • 越来越多的项目是基于TS的,比如VSCode、Angular6、Vue3、React16
  • TS提供的类型系统可以帮助我们在写代码的时候提供更丰富的语法提示
  • 在创建前的编译阶段经过类型系统的检查,就可以避免很多线上的错误

TypeScript安装和编译

npm
npm i typescript -g
tsc helloworld.ts

生成配置文件

npm
tsc --init
json
{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. 指定ECMAScript的目标版本*/
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. 指定模块代码的生成方式*/
    // "lib": [],                             /* Specify library files to be included in the compilation. 指定编译的时候用来包含的编译文件*/
    // "allowJs": true,                       /* Allow javascript files to be compiled. 允许编译JS文件*/
    // "checkJs": true,                       /* Report errors in .js files. 在JS中包括错误*/
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. 指定JSX代码的生成方式 是保留还是react-native或者react*/
    // "declaration": true,                   /* Generates corresponding '.d.ts' file.生成相应的类型声明文件 */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. 为每个类型声明文件生成相应的sourcemap*/
    // "sourceMap": true,                     /* Generates corresponding '.map' file. 生成对应的map文件 */
    // "outFile": "./",                       /* Concatenate and emit output to single file. 合并并且把编译后的内容输出 到一个文件里*/
    // "outDir": "./",                        /* Redirect output structure to the directory.按原始结构输出到目标目录 */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. 指定输入文件的根目录,用--outDir来控制输出的目录结构*/
    // "composite": true,                     /* Enable project compilation 启用项目编译*/
    // "removeComments": true,                /* Do not emit comments to output. 移除注释*/
    // "noEmit": true,                        /* Do not emit outputs. 不要输出*/
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. 当目标是ES5或ES3的时候提供对for-of、扩展运算符和解构赋值中对于迭代器的完整支持*/
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule').r把每一个文件转译成一个单独的模块 */

    /* Strict Type-Checking Options */
    //"strict": true,                           /* Enable all strict type-checking options. 启用完全的严格类型检查 */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. 不能使用隐式的any类型*/
    // "strictNullChecks": true,              /* Enable strict null checks. 启用严格的NULL检查*/
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. 启用严格的函数类型检查*/
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions.启用函数上严格的bind call 和apply方法 */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. 启用类上初始化属性检查*/
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type.在默认的any中调用 this表达式报错 */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. 在严格模式下解析并且向每个源文件中发射use strict*/

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. 有未使用到的本地变量时报错 */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. 有未使用到的参数时报错*/
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. 当不是所有的代码路径都有返回值的时候报错*/
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. 在switch表达式中没有替代的case会报错 */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). 指定模块的解析策略 node classic*/
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. 在解析非绝对路径模块名的时候的基准路径*/
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. 一些路径的集合*/
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. 根目录的列表,在运行时用来合并内容*/
    // "typeRoots": [],                       /* List of folders to include type definitions from. 用来包含类型声明的文件夹列表*/
    // "types": [],                           /* Type declaration files to be included in compilation.在编译的时候被包含的类型声明 */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking.当没有默认导出的时候允许默认导入,这个在代码执行的时候没有作用,只是在类型检查的时候生效 */
    //"esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.*/
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks.不要symlinks解析的真正路径 */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. 指定ts文件位置*/
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. 指定 map文件存放的位置 */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. 源文件和sourcemap 文件在同一文件中,而不是把map文件放在一个单独的文件里*/
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. 源文件和sourcemap 文件在同一文件中*/

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. 启动装饰器*/
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  }
}

布尔类型(boolean)

ts
let n: boolean=false;

数字类型(number)

ts
let n: number=0;

字符串类型(string)

ts
let n: string='hl';

数组类型(array)

ts
let n1: number[]=[1];
let n2: Array<number>=[1];

元组类型(tuple)

  • 在 TypeScript 的基础类型中,元组( Tuple )表示一个已知数量和类型的数组
ts
let n:[string,number] = ['hl',5];
n[0].length;
n[1].toFixed(2);

任意类型(any)

  • any就是可以赋值给任意类型
ts
let root:any={}
root = 1
root = {}

let root:(HTMLElement|null)=document.getElementById('root');
root!.style.color='red';//非空断言操作符

null 和 undefined

  • null 和 undefined 是其它类型的子类型,非严格模式下可以赋值给其它类型,如数字类型,此时,赋值后的类型会变成 null 或 undefined
  • strictNullChecks 参数用于新的严格空检查模式,在严格空检查模式下, null 和 undefined 值都不属于任何一个类型,它们只能赋值给自己这种类型或者 any
js
let x: number;
x = 1;
x = undefined;    
x = null;   

let y: number | null | undefined;
y = 1;
y = undefined;   
y = null;

void 类型

  • void 表示没有任何类型
  • 当一个函数没有返回值时,TS 会认为它的返回值是 void 类型
ts
function greeting(name:string):void {
    console.log('hello',name);
    //当我们声明一个变量类型是 void 的时候,它的非严格模式(strictNullChecks:false)下仅可以被赋值为 null 和 undefined
    //严格模式(strictNullChecks:true)下只能返回undefined
    //return null;
    //return undefined;
}

never类型

  • never是其它类型(null undefined)的子类型,代表不会出现的值
ts
// 返回never的函数 必须存在 无法达到( unreachable ) 的终点
function error(message: string): never {
    throw new Error(message);
}
let result1 = error('hello');
// 由类型推论得到返回值为 never
function fail() {
    return error("Something failed");
}
let result = fail();

// 返回never的函数 必须存在 无法达到( unreachable ) 的终点
function infiniteLoop(): never {
    while (true) {}
}

never 和 void 的区别

  • void 可以被赋值为 null 和 undefined的类型。 never 则是一个不包含值的类型

Symbol

  • 我们在使用 Symbol 的时候,必须添加 es6 的编译辅助库
  • Symbol 是在ES2015之后成为新的原始类型,它通过 Symbol 构造函数创建
  • Symbol 的值是唯一不变的
ts
const sym1 = Symbol('key');
const sym2 = Symbol('key');
Symbol('key') === Symbol('key') // false

枚举类型(enum)

普通枚举

  • 不指定值,默认从0开始
  • 当上一个指定的值,不是数字类型,下面的必须设置值
ts
 enum Colors {
    Red='Red',
    Yellow=0,
    Blue
}
let myColors = [Colors.Red, Colors.Yellow, Colors.Blue]; // [Red,0,1]
enum Gender{
    GIRL,
    BOY
}
console.log(Gender.GIRL); // 0
console.log(Gender.BOY); // 0

enum Week{
    MONDAY=1,
    TUESDAY=2
}
console.log(`今天是星期${Week.MONDAY}`); // 1

常数枚举

  • 常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
  • 假如包含了计算成员,则会在编译阶段报错
ts
const enum Colors {
    Red,
    Yellow,
    Blue
}

let myColors = [Colors.Red, Colors.Yellow, Colors.Blue]; // [0,1,2]

const enum Colors {
    Red,
    Yellow,
    Blue=Blue = "blue" // 包含了计算成员,编译阶段报错

}

类型自动推断

  • 是指编程语言中能够自动推导出值的类型的能力,它是一些强静态类型语言中出现的特性
  • 定义时未赋值就会推论成any类型
  • 如果定义的时候就赋值就能利用到类型推论
ts
// 定义时未赋值就会推论成any类型
let n;
n=10;
n=[]

// 如果定义的时候就赋值就能利用到类型推论
let n = 10;
n= '1' // Type 'string' is not assignable to type 'number'.(2322)

联合类型

  • 联合类型(Union Types)表示取值可以为多种类型中的一种

联合类型 not strictNullChecks

  • strictNullChecks模式下,未赋值时只能访问两个类型共有的属性和方法
ts
let n: string | number;
// 非【strictNullChecks】模式下,未赋值时只能访问两个类型共有的属性和方法
console.log(n.toString());
n = 3;
console.log(n.toFixed(2));
n = 'zhufeng';
console.log(n.length);

联合类型和交叉类型

  • 联合类型:符号是 | ,代表了按位或,即只需要符合联合类型中的一个类型,可以认为实现了这个联合类型,如 A | B ,只需要实现 A 或 B 即可。
  • 交叉类型:符号是 & ,代表了按位或,需要符合这里的所有类型,才可以说实现了这个交叉类型,即 A & B ,需要同时满足 A 与 B 两个类型才行。
ts
interface A {
  name: string;
}

interface B {
  age: number;
}

type C = A & B;

const profile: C = {
  name: "a",
  age: 18
}
// 类型不可能即使字符又是数字
type D = string & number; // never

对象类型的交叉类型

  • 对象类型会合并
ts
type A = {
  a: string;
  b: {
    name: string;
  }
}

type B = {
  a: number;
  b: {
    age: number;
  }
}

type C = A & B;


type D = C['a']; // never
type E = C['b']; // { name: string; age: number; }

联合类型 strictNullChecks

  • strictNullChecks模式下,未赋值时不能访问任何属性和方法

strictNuLIChecks1

ts
let n: string | number;
// 【strictNullChecks】模式下,未赋值时不能访问任何属性和方法
console.log(n.toString()); // Variable 'n' is used before being assigned.(2454)
n = 3;
console.log(n.toFixed(2));
n = 'zhufeng';
console.log(n.length);

类型断言

  • 类型断言可以将一个联合类型的变量,指定为一个更加具体的类型
  • 不能将联合类型断言为不存在的类型

类型断言 not strictNullChecks

ts
let n: string | number;
console.log((n as string).length);
console.log((n as number).toFixed(2));
// 不能将联合类型断言为不存在的类型
console.log((n as boolean));

类型断言 strictNullChecks

  • strictNullChecks模式下,未赋值时不能类型断言

strictNuLIChecks2

双重断言

  • 先断言成任意类型,在断言成具体的类型
ts
interface UserInfo {
    name: string;
    age: number;
}
const userInfo = 'hl' as any as UserInfo;


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

function formatUser(userInfo:UserInfo){

}

fetch('http://user').then(res=>test(res.json as unknown as UserInfo) )

字面量类型和类型字面量

  • 字面量类型的要和实际的值的字面量一一对应,如果不一致就会报错
  • 类型字面量和对象字面量的语法很相似
ts
const up:'Up'= 'Up';
const down: "Down" = "Down";
const left: "Left" = "Left";
const right: "Right" = "Right";
type Direction = 'Up' | 'Down' | 'Left' | 'Right';
function move(direction: Direction) {}
move("Up");

函数参数类型

ts
function hello(name:string):void {
    console.log('hello',name);
}
hello('hl');

函数类型约束

ts
type typeAdd = (x:number,y:number)=>number;
let add:typeAdd = (x,y)=>{
  return x + y;
}
add(1,2)

函数可选参数

  • 在TS中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数
ts
function print(name:string,age?:number):void {
    console.log(name,age);
}
print('hl');

函数默认参数

ts
function ajax(url:string,method:string='GET') {
    console.log(url,method);
}
ajax('/users');

函数剩余参数

ts
function sum(...numbers:number[]) {
    return numbers.reduce((val,item)=>val+=item,0);
}
console.log(sum(1,2,3));

函数重载

  • 在Java中的重载,指的是两个或者两个以上的同名函数,参数不一样
  • 在TypeScript中,表现为给同一个函数提供多个函数类型定义
ts
let obj: any={};
function attr(val: string): void;
function attr(val: number): void;
function attr(val:any):void {
    if (typeof val === 'string') {
        obj.name=val;
    } else {
        obj.age=val;
    }
}
attr('zfpx');
attr(9);
attr(true);
console.log(obj);

类参数属性

ts
class User {
    public name:string;
    public age:number
    constructor(name:string,age:number) {
        this.name = name;
        this.age = age;
    }
}

let user = new User('hl',20);
console.log(user.name); 
console.log(user.age);


// 等价于User
class User1 {
    constructor(public name:string,public age:number) {}
}
let user1 = new User1('hl',20);
console.log(user1.name); 
console.log(user1.age);

类属性修饰符

  • static: 被static修饰的属性、方法通过类名调用
  • public: 被public修饰的属性、方法本类、子类、实例对象都可以直接访问
  • protected :被protected修饰的属性、方法只能在本类或者子类中访问
  • private: 被private修饰的属性、方法只能在本类中访问
  • readonly: 被readonly修饰的属性只能在构造函数中初始化
ts
class User {
    static className='User';
    public name: string;  
    protected age: number; 
    private money: number; 

    constructor(name:string,age:number,money:number) {
        this.name = name;
        this.age = age;
        this.money = money
    }

    static getClassName() {
        return User.className;
    }

     getMoney():number{
        return this.money
    }

    setMoney(money:number):void{
        this.money = money
    }
}

let user = new User('hl',20,100000);

类的继承

  • 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
  • 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
  • super可以调用父类上的方法和属性
ts
class Person {
    name: string;
    age: number;
    constructor(name:string,age:number) {
        this.name=name;
        this.age=age;
    }
    getName():string {
        return this.name;
    }
    setName(name:string): void{
        this.name=name;
    }
}
class Student extends Person{
    no: number;
    constructor(name:string,age:number,no:number) {
        super(name,age);
        this.no=no;
    }
    getNo():number {
        return this.no;
    }
}
let s1=new Student('hl',10,1);
console.log(s1);

装饰器

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为
  • 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
  • 装饰器的写法分为普通装饰器和装饰器工厂

类装饰器

  • 类装饰器在类声明之前声明,用来监视、修改或替换类定义
  • 如果一个类中有多个装饰器,会从内往外依次执行
ts
//当装饰器作为修饰类的时候,会把构造器传递进去(类本身)
function addNameEat(constructor: Function) {
  // 在构造器原型上添加属性、和方法
  // 当实例对象本身没有,会通过__proto__找prototype
  constructor.prototype.name = "hl";
   constructor.prototype.age = 20
  constructor.prototype.eat = function () {
    console.log("eat");
  };
}

@addNameEat
class Person {
  name!: string;
  eat!: Function;
  age!: number;
  constructor() {
    this.age = 21
  }
}
let p: Person = new Person();
console.log(p.name); // hl
console.log(p.age); // 21
p.eat(); // eat

抽象类、抽象方法

  • 抽象类,无法被实例化,只能被继承
  • 抽象方法不能包含方法体
js
abstract class Animal{
    abstract speak():void;
}
class Dog extends Animal{
  // 必须实现抽象方法
  speak(){
      console.log('小狗汪汪汪');
  }
}
let dog=new Dog();
dog.speak();

接口

  • 接口一方面可以在面向对象编程中表示为行为的抽象
  • 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
  • 一个类可以继承另一个类并实现多个接口

类可以实现多个接口

  • 类必须实现所有的接口方法
ts
interface Speakable{
    speak():void;
}
interface Eatable{
    eat():void
}
class Person implements Speakable,Eatable{
    speak(){
        console.log('Person说话');
    }
    eat(){}
}

接口约束任意属性

ts
interface Person {
  readonly id: number;
  name: string;
  [propName: string]: any;
}

let p1 = {
  id:1,
  name:'zhufeng',
  age:10
}

接口可以继承接口

ts
interface Speakable {
    speak(): void
}
interface SpeakChinese extends Speakable {
    speakChinese(): void
}
class Person implements SpeakChinese {
    speak() {
        console.log('Person')
    }
    speakChinese() {
        console.log('speakChinese')
    }
}

函数类型接口

  • 对方法传入的参数和返回值进行约束
ts
interface discount{
  (price:number):number
}
let cost:discount = function(price:number):number{
   return price * .8;
}

可索引接口

  • 对数组和对象进行约束
  • userInterface 表示index的类型是 number,那么值的类型必须是 string
  • UserInterface2 表示:index 的类型是 string,那么值的类型必须是 string
ts
interface UserInterface {
  [index:number]:string
}
let arr:UserInterface = ['h1','h2'];
console.log(arr[0]); // h1
console.log(arr['1']); // h2
console.log(arr['a']);  // Element implicitly has an 'any' type because index expression is not of type 'number'.(7015)

interface UserInterface2 {
  [index:string]:string
}
let obj:UserInterface2 = {name:'hl'};

对类的约束

ts
interface Speakable {
    name: string;
    speak(words: string): void
}
class Dog implements Speakable {
    name!: string;
    speak(words:string) {
        console.log(words);
    }
}
let dog = new Dog();
dog.speak('汪汪汪');

构造函数的类型

  • 在 TypeScript 中,我们可以用 interface 来描述类
  • 同时也可以使用interface里特殊的new()关键字来描述类的构造函数类型
ts
class Animal{
  constructor(public name:string){
  }
}
//不加new是修饰函数的,加new是修饰类的
interface WithNameClass{
  new(name:string):Animal
}
function createAnimal(clazz:WithNameClass,name:string){
   return new clazz(name);
}
let a = createAnimal(Animal,'hl');
console.log(a.name);

抽象类 vs 接口

  • 不同类之间公有的属性或方法,可以抽象成一个接口(Interfaces)
  • 而抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,既不提供方法的实现,也不为属性进行初始化
  • 一个类可以继承一个类或抽象类,但可以实现(implements)多个接口
  • 抽象类也可以实现接口

泛型

  • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
  • 泛型T作用域只限于函数内部使用

泛型函数

ts
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
      result[i] = value;
    }
    return result;
  }
let result = createArray2<string>(3,'x');
console.log(result);

泛型类

ts
class MyArray<T>{
    private list:T[]=[];
    add(value:T) {
        this.list.push(value);
    }
    getMax():T {
        let result=this.list[0];
        for (let i=0;i<this.list.length;i++){
            if (this.list[i]>result) {
                result=this.list[i];
            }
        }
        return result;
    }
}
let arr=new MyArray<number>();
arr.add(1); 
arr.add(2); 
arr.add(3);
let ret = arr.getMax();
console.log(ret);

泛型接口

ts
interface Calculate{
  <T>(a:T,b:T):T
}
let add:Calculate = function<T>(a:T,b:T){
  return a;
}
add<number>(1,2);

多个类型参数

ts
function swap<A,B>(tuple:[A,B]):[B,A]{
  return [tuple[1],tuple[0]];
}
let swapped = swap<string,number>(['a',1]);
console.log(swapped);
console.log(swapped[0].toFixed(2));
console.log(swapped[1].length);

默认泛型类型

ts
function createArray3<T=number>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
let result2 = createArray3(3,'x');
console.log(result2);

泛型约束

  • 在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性或方法。
ts
function logger<T>(val: T) {
    console.log(val.length); //直接访问会报错
}
//可以让泛型继承一个接口
interface LengthWise {
    length: number
}
//可以让泛型继承一个接口
function logger2<T extends LengthWise>(val: T) {
    console.log(val.length)
}
logger2('hl');
logger2(1);

泛型接口

ts
interface Cart<T>{
  list:T[]
}
let cart:Cart<{name:string,price:number}> = {
  list:[{name:'zhufeng',price:10}]
}
console.log(cart.list[0].name,cart.list[0].price);

泛型类型别名

ts
type Cart<T> = {list:T[]} | T[];
let c1:Cart<string> = {list:['1']};
let c2:Cart<number> = [1];

泛型接口 vs 泛型类型别名

  • 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
  • 类型别名不能被 extends和 implements,这时我们应该尽量使用接口代替类型别名
  • 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适

接口的兼容性

  • 只有在传参的时候两个变量之间才会进行兼容性的比较,赋值的时候并不会比较,会直接报错
ts
interface Animal {
    name: string;
    age: number;
}

interface Person {
    name: string;
    age: number;
    gender: number
}
// 只有在传参的时候两个变量之间才会进行兼容性的比较
function getName(animal: Animal): string {
    return animal.name;
}

let p = {
    name: 'zhufeng',
    age: 10,
    gender: 0
}

getName(p);
// 赋值的时候并不会比较,会直接报错
let a: Animal = {
    name: 'zhufeng',
    age: 10,
    gender: 0 // Type '{ name: string; age: number; gender: number; }' is not assignable to type 'Animal'.
}

基本类型的兼容性

ts
//基本数据类型也有兼容性判断
let num : string|number;
let str:string='zhufeng';
num = str;

//只要有toString()方法就可以赋给字符串变量
let num2 : {
  toString():string
}

let str2:string='jiagou';
num2 = str2;

类的兼容性

  • 在TS中是结构类型系统,只会对比结构而不在意类型
ts
class Animal{
  name:string
}
class Bird extends Animal{
   swing:number
}

let a:Animal;
a = new Bird();

let b:Bird;
// 不能被兼容,属性不一样
b = new Animal();
ts
class Animal{
  name:string
}
//如果父类和子类结构一样,也可以的
class Bird extends Animal{}

let a:Animal;
a = new Bird();

let b:Bird;
b = new Animal();



//甚至没有关系的两个类的实例也是可以的
class Animal{
  name:string
}
class Bird{
  name:string
}
let a:Animal ;
a = new Bird();
let b:Bird;
b = new Animal();

函数的兼容性

ts
type sumFunc = (a:number,b:number)=>number;
let sum:sumFunc;

function f1(a:number,b:number):number{
  return a+b;
}
sum = f1;

//可以省略一个参数
function f2(a:number):number{
   return a;
}
sum = f2;

//可以省略二个参数
function f3():number{
    return 0;
}
sum = f3;

 //多一个参数可不行
function f4(a:number,b:number,c:number){
    return a+b+c;
}
sum = f4;

比较返回值

ts
type GetPerson = ()=>{name:string,age:number};
let getPerson:GetPerson;
//返回值一样可以
function g1(){
    return {name:'zhufeng',age:10};
}
getPerson = g1;
//返回值多一个属性也可以
function g2(){
    return {name:'zhufeng',age:10,gender:'male'};
}
getPerson = g2;
//返回值少一个属性可不行
function g3(){
    return {name:'zhufeng'};
}
getPerson = g3;
//因为有可能要调用返回值上的方法
getPerson().age.toFixed();

泛型的兼容性

  • 泛型在判断兼容性的时候会先判断具体的类型,然后再进行兼容性判断
ts
//1.接口内容为空没用到泛型的时候是可以的
interface Empty<T>{}
let x!:Empty<string>;
let y!:Empty<number>;
x = y;

//2.接口内容不为空的时候不可以
interface NotEmpty<T>{
  data:T
}
let x1!:NotEmpty<string>;
let y1!:NotEmpty<number>;
x1 = y1;

// 实现原理如下,称判断具体的类型再判断兼容性
interface NotEmptyString{
    data:string
}

interface NotEmptyNumber{
    data:number
}
let xx2!:NotEmptyString;
let yy2!:NotEmptyNumber;
xx2 = yy2; // 类型不意义不能兼容

枚举的兼容性

  • 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容
  • 不同枚举类型之间是不兼容的
ts
//数字可以赋给枚举
enum Colors {Red,Yellow}
let c:Colors;
c = Colors.Red;
c = 1;
c = '1';

//枚举值可以赋给数字
let n:number;
n = 1;
n = Colors.Red;

类型保护

  • 类型保护就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型
  • 类型保护就是能够通过关键字判断出分支中的类型

typeof 类型保护

ts
function double(input: string | number | boolean) {
    if (typeof input === 'string') {
        return input + input;
    } else {
        if (typeof input === 'number') {
            return input * 2;
        } else {
            return !input;
        }
    }
}

instanceof类型保护

ts
class Animal {
    name!: string;
}
class Bird extends Animal {
    swing!: number
}
function getName(animal: Animal) {
    if (animal instanceof Bird) {
        console.log(animal.swing);
    } else {
        console.log(animal.name);
    }
}

null保护

  • 如果开启了strictNullChecks选项,那么对于可能为null的变量不能调用它上面的方法和属性
ts
function getFirstLetter(s: string | null) {
    //第一种方式是加上null判断
    if (s == null) {
        return '';
    }
    //第二种处理是增加一个或的处理
    s = s || '';
    return s.charAt(0);
}
//它并不能处理一些复杂的判断,需要加非空断言操作符
function getFirstLetter2(s: string | null) {
    function log() {
        console.log(s!.trim());
    }
    s = s || '';
    log();
    return s.charAt(0);
}

链判断运算符

  • 链判断运算符是一种先检查属性是否存在,再尝试访问该属性的运算符,其符号为 ?.
  • 如果运算符左侧的操作数 ?. 计算为 undefined 或 null,则表达式求值为 undefined 。否则,正常触发目标属性访问,方法或函数调用。
ts
a?.b; //如果a是null/undefined,那么返回undefined,否则返回a.b的值.
a == null ? undefined : a.b;

a?.[x]; //如果a是null/undefined,那么返回undefined,否则返回a[x]的值
a == null ? undefined : a[x];

a?.b(); // 如果a是null/undefined,那么返回undefined
a == null ? undefined : a.b(); //如果a.b不函数的话抛类型错误异常,否则计算a.b()的结果

a?.(); //如果a是null/undefined,那么返回undefined
a == null ? undefined : a(); //如果A不是函数会抛出类型错误
//否则 调用a这个函数

可辨识的联合类型

  • 就是利用联合类型中的共有字段进行类型保护的一种技巧
  • 相同字段的不同取值就是可辨识
ts
interface WarningButton{
  class:'warning',
  text1:'修改'
}
interface DangerButton{
  class:'danger',
  text2:'删除'
}
type Button = WarningButton|DangerButton;
function getButton(button:Button){
 if(button.class=='warning'){
  console.log(button.text1);
 }
 if(button.class=='danger'){
  console.log(button.text2);
 }
}

类型字面量+可辨识联合类型

ts
interface User {
    username: string
}
type Action = {
    type:'add',
    payload:User
} | {
    type: 'delete'
    payload: number
}
const UserReducer = (action: Action) => {
  switch (action.type) {
    case "add":
      let user: User = action.payload;
      break;
    case "delete":
      let id: number = action.payload;
      break;
    default:
      break;
  }
};

in操作符

  • in 运算符可以被用于参数类型的判断
ts
interface Bird {
    swing: number;
}

interface Dog {
    leg: number;
}

function getNumber(x: Bird | Dog) {
    if ("swing" in x) {
      return x.swing;
    }
    return x.leg;
}

自定义的类型保护

  • TypeScript 里的类型保护本质上就是一些表达式,它们会在运行时检查类型信息,以确保在某个作用域里的类型是符合预期的
  • type is Type1Class就是类型谓词
  • 谓词为 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名 -每当使用一些变量调用isType1时,如果原始类型兼容,TypeScript会将该变量缩小到该特定类型
ts
function isType1(type: Type1Class | Type2Class): type is Type1Class {
    return (<Type1Class>type).func1 !== undefined;
}

// -------------------

interface Bird {
  swing: number;
}

interface Dog {
  leg: number;
}

//没有相同字段可以定义一个类型保护函数
function isBird(x:Bird|Dog): x is Bird{
  return (<Bird>x).swing == 2;
  //return (x as Bird).swing == 2;
}

function getAnimal(x: Bird | Dog) {
  if (isBird(x)) {
    return x.swing;
  }
  return x.leg;
}

unknown vs any区别

  • TypeScript 3.0 引入了新的unknown 类型,它是 any 类型对应的安全类型
  • unknown 和 any 的主要区别是 unknown 类型会更加严格
    • 在对 unknown 类型的值执行操作之前,我们必须进行某种形式的检查。
    • 而在对 any 类型的值执行操作之前,我们不必进行任何检查

any 类型

  • 在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的 顶级类型 (也被称作 全局超级类型)。
  • TypeScript允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查
ts
let value: any;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK


let value: any;
value.foo.bar;  // OK
value.trim();   // OK
value();        // OK
new value();    // OK

unknown

  • 就像所有类型都可以被归为 any,所有类型也都可以被归为 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)
  • 任何类型都可以赋值给unknown类型
  • unknown类型的变量只能被赋值给any类型unknown类型本身
ts
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

// unknown类型的变量只能被赋值给`any类型`和`unknown类型`本身
let value2:any = value // OK
let value3:unknown = value // OK
let value4:string = value // Type 'unknown' is not assignable to type 'string'.

// 可以进行类型断言
let value5:string = value  as  string // OK

联合类型中的 unknown 类型

  • 在联合类型中,unknown 类型会吸收任何类型。这就意味着如果任一组成类型是 unknown,联合类型也会相当于 unknown
ts
type UnionType1 = unknown | null;       // unknown
type UnionType2 = unknown | undefined;  // unknown
type UnionType3 = unknown | string;     // unknown
type UnionType4 = unknown | number[];   // unknown

交叉类型中的 unknown 类型

  • 在交叉类型中,任何类型都可以吸收 unknown 类型。这意味着将任何类型与 unknown 相交不会改变结果类型
ts
type IntersectionType1 = unknown & null;       // null
type IntersectionType2 = unknown & undefined;  // undefined
type IntersectionType3 = unknown & string;     // string
type IntersectionType4 = unknown & number[];   // number[]
type IntersectionType5 = unknown & any;        // any

never是unknown的子类型

ts
type isNever = never extends unknown ? true : false;
let a:isNever= true // OK
let a:isNever= false // error

keyof unknown 等于never

  • never不可达
ts
type key = keyof unknown;

function throwError(message: string): key {
  throw new Error(message);
}

function infiniteLoop(): key {
  while (true) {
    // do something...
  }
}

只能对unknown进行等或不等操作,不能进行其它操作

ts
let un1:unknown = 0
let un2:unknown = 1
un1===un2; 
un1!==un2;
un1 = un2;
un1 + un2; // Error

unknown不能做任何操作

  • 不能访问属性
  • 不能作为函数调用
  • 不能当作类的构造函数不能创建实例
ts
let un1:unknown = {name:'10'}
un1.name // Error

let fn:unknown=function(){}
fn(); // Error
new fn(); // Error

交叉类型

  • 交叉类型(Intersection Types)是将多个类型合并为一个类型
  • 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
ts
interface Bird {
    name: string,
}
interface Person {
    age: number,
}
type BirdPerson = Bird & Person;
let p: BirdPerson = { name: 'hl',age:10 };

联合类型的交叉类型

  • 联合类型的交叉类型取相交的类型
  • 如果没有相交的类型则是never类型
ts
type Ta = string | number;
type Tb = number | boolean;
type Tc = Ta & Tb;
let a:Tc = 10; // ok
let b:Tc = 'a'; // Error

// 
type Ta = string;
type Tb =  boolean;
type Tc = Ta & Tb;
let a:Tc = '1' // Type 'string' is not assignable to type 'never'

typeof

  • 可以获取一个变量的类型
ts
//先定义类型,再定义变量
type People = {
    name:string,
    age:number,
    gender:string
}
let p1:People = {
    name:'hl',
    age:10,
    gender:'male'
}

//先定义变量,再通过typeof定义类型
let p1 = {
    name:'hl',
    age:10,
    gender:'male'
}
type People = typeof p1;
function getName(p:People):string{
    return p.name;
}
getName(p1);

keyof

  • 索引类型查询操作符
ts
interface Person{
  name:string;
  age:number;
  gender:'male'|'female';
}
//type PersonKey = 'name'|'age'|'gender';
type PersonKey = keyof Person;

function getValueByKey(p:Person,key:PersonKey){
  return p[key];
}
let val = getValueByKey({name:'zhufeng',age:10,gender:'male'},'name'); // key => 'name'|'age'|'gender';
console.log(val);

映射类型(接口中的属性都变成可选的)

  • 在定义的时候用in操作符去批量定义类型中的属性
ts
nterface Person{
  name:string;
  age:number;
  gender:'male'|'female';
}
//批量把一个接口中的属性都变成可选的
type PartPerson = {
  [Key in keyof Person]?:Person[Key]
}

let p1:PartPerson={};
//也可以使用泛型
type Part<T> = {
  [key in keyof T]?:T[key]
}
let p2:Part<Person>={};

通过key的数组获取值的数组

  • K extends keyof T,K只能是T的子项
ts
function pick<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map((n) => o[n]);
}
let user = { id: 1, name: 'hl' };
type User = typeof user;
const res = pick<User, keyof User>(user, ["id", "name"]);
console.log(res);

条件类型

  • 定义泛型的时候能够添加进逻辑分支,以后泛型更加灵活
ts
interface Fish {
    name: string
}
interface Water {
    name: string
}
interface Bird {
    name: string
}
interface Sky {
    name: string
}
//若 T 能够赋值给 Fish,那么类型是 Water,否则为 Sky
type Condition<T> = T extends Fish ? Water : Sky;
let condition: Condition<Fish> = { name: '水' };

条件类型的分发

ts
interface Fish {
    fish: string
}
interface Water {
    water: string
}
interface Bird {
    bird: string
}
interface Sky {
    sky: string
}
//naked type
type Condition<T> = T extends Fish ? Water : Sky;

// (Fish extends Fish ? Water : Sky) | (Bird extends Fish ? Water : Sky)
// Water|Sky
let condition1: Condition<Fish | Bird> = { water: '水' };
let condition2: Condition<Fish | Bird> = { sky: '天空' };

找出T类型中U不包含的部分

ts
//never会被自动过滤
type Diff<T, U> = T extends U ? never : T;

type R = Diff<"a" | "b" | "c" , "a" | "b">;  // "c"

let diff:R = 'c'

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

type R1 = Diff1<"a" | "b" | "c" , "a" | "b">;  // "a" | "b"

let diff1:R1 = 'a'
diff1 = 'b'

Exclude(T排除U)

ts
type Exclude1<T,U> = T extends U ? never : T
let age:Exclude1<string|number,string> = 10

Exclude(T提取U)

ts
type Extract1<T,U> = T extends U ? T : never
let username:Extract1<string|number,string> = 'hl'

NonNullable

  • 从 T 中排除 null 和 undefined
ts
type NonNullable<T> = T extends null | undefined ? never : T;

type  E = NonNullable<string|number|null|undefined>;
let e:E = null; // Error

ReturnType

  • infer最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量
  • 获取函数类型的返回类型
ts
function getUserInfo() {
    return { name: "hl", age: 10 };
}
// 通过 ReturnType 将 getUserInfo 的返回值类型赋给了 UserInfo
// type UserInfo = {
//     name: string;
//     age: number;
// }
type UserInfo = ReturnType<typeof getUserInfo>;

// ReturnType源码实现
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
function getUserInfo() {
    return { name: "hl", age: 10 };
}

type UserInfo = ReturnType<typeof getUserInfo>;

const userA: UserInfo = {
    name: "zhufeng",
    age: 10
};

Parameters

  • 构造函数类型T的形参类型的元组类型
ts
type Parameters<T> = T extends (...args: infer R) => any ? R : any;

type T0 = Parameters<() => string>;  // []
type T1 = Parameters<(s: string) => void>;  // [string]
type T2 = Parameters<(<T>(arg: T) => T)>;  // [unknown]

InstanceType

  • 获取构造函数类型的实例类型
ts
type Constructor = new (...args: any[]) => any;
type ConstructorParameters<T extends Constructor> = T extends new (...args: infer P) => any ? P : never;
type InstanceType<T extends Constructor> = T extends new (...args: any[]) => infer R ? R : any;

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    getName() { console.log(this.name) }
}
//构造函数参数
type constructorParameters = ConstructorParameters<typeof Person>;
let params: constructorParameters = ['zhufeng']
//实例类型
type Instance = InstanceType<typeof Person>;
let instance: Instance = { name: 'zhufeng', getName() { } };

Partial(属性可选)

  • Partial 可以将传入的属性由非可选变为可选
ts
type Partial<T> = { [P in keyof T]?: T[P] };

interface IUser{
   name:string
   age:number 
}

let user: IUser ={name:'hl',age:10}

let user1: Partial<IUser> ={name:'hl'}

Required

  • Required 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。
ts
type Required<T> ={[ P in keyof T]-?:T[P]}

interface IUser{
   name?:string
   age?:number 
}

let user: Required<IUser> ={name:'hl',age:19}

Readonly

  • Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现。
ts
type Readonly<T> ={readonly [ P in keyof T]:T[P]}

interface IUser{
  name:string
  age:number 
}
let user: Readonly<IUser> ={name:'hl',age:19}
user.age = 10 // Cannot assign to 'age' because it is a read-only property.(2540)

Pick(保留)

  • Pick 能够帮助我们从传入的属性中摘取某一项返回
ts

type Pick1<T,K extends keyof T> ={[P in K]:T[P]} 

interface IUser {
  name: string;
  age: number;
  add:string
}

// K 必须是T内部的属性
// K 只能是'name'|'age'|'add'
let user:Pick1<IUser,'name'|'age'> = {name:'hl',age:10}

Omit(剔除)

  • Exclude 的作用是从 T 中排除出可分配给 U的元素.
  • Omit<T, K>的作用是忽略T中的某些属性
  • Omit = Exclude + Pick
ts
type Pick1<T,K extends keyof T> ={[P in K]:T[P]} 

type SetDifference<A, B> = A extends B ? never : A;

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

let user:Omit<IUser,'age'> = {name:'hl'}

Record

  • Record 是 TypeScript 的一个高级类型
  • 他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
ts
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

type Point = 'x' | 'y';
type PointList = Record<Point, { value: number }>
const cars: PointList = {
    x: { value: 10 },
    y: { value: 20 },
}

Mutable

  • 将 T 的所有属性的 readonly 移除
ts
type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}

Merge

  • Merge<O1, O2>的作用是将两个对象的属性合并:
  • Merge<O1, O2> = Compute + Omit<U, T>
ts
type O1 = {
  id: number;
  name: string;
};

type O2 = {
  id: number;
  age: number;
};


//Compute的作用是将交叉类型合并
type Compute<A extends any> = A extends Function ? A : { [K in keyof A]: A[K] };

type R1 = Compute<{ x: "x" } & { y: "y" }>;
type Merge<O1 extends object, O2 extends object> = Compute<
  O1 & Omit<O2, keyof O1>
>;

type R2 = Merge<O1, O2>;

类型声明

  • 声明文件可以让我们不需要将JS重构为TS,只需要加上声明文件就可以使用系统
  • 类型声明在编译的时候都会被删除,不会影响真正的代码
  • 关键字 declare 表示声明的意思,我们可以用它来做出各种声明
ts
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface type 声明全局类型

普通类型声明

ts
declare let name: string;  //变量
declare let age: number;  //变量
declare function getName(): string;  //方法
declare class Animal { name: string }  //类
console.log(name, age);
getName();
new Animal();
export default {};

// 声明jQuery对象
declare const $: (selector: string) => { //变量
    click(): void;
    width(length: number): void;
};
$('#root').click();
console.log($('#root').width);