typescript 进阶

122 阅读14分钟

类和继承

类修饰符

abstract ,不能new,只能被子类实现

属性访问符

private protected public abstract readonly

类多态

  1. 子类继承父类,重写父类的方法
  2. 子类方法的访问修饰符要大于父类
  3. ==参数和返回是否有要求?==

属性定义未初始化报错处理

// 方案1
class A {
  construtor(public a:number)
}
// 方案2 ,叹号,ts4以上
class A{
  public a!: number
}
// 方案3
class A{
  a:string;
  constructor(a:string){
     this.a = a 
  }
}

js实现

class A extends B { }
// 两方面继承,原型和静态
A.__proto__ === B
A.prototype.__proto__ === B.prototype

//js实现 (组合寄生)
// 静态继承
function extendsStatics(Son, Parent) {
  for (let k in Parent) {
    if (Object.prototype.hasOwnProperty.call(Parent, k)) {
      Son[k] = Parent[k];
    }
  }
}
// 原型继承
function extendFn(Son, Parent) {
  extendsStatics(Son, Parent); // 静态
  function Middle() {
    this.constructor = Son; // 重要 Son.prototype.constructor= Son
  }
  Middle.prototype = Parent.prototype;
  Son.prototype = new Middle(); // 原型继承寄生组合
}
// class B
function B(age) {
  this.age = age;
}
B.prototype.getAge = function () {
  return this.age;
};
// class A
function A(name, age) {
  this.name = name;
  B.call(this, age);
}
extendFn(A, B);
A.prototype.afn=()=>{}
// A.__proto__ = B  //没法new实现,new 的话就是,xx.__proto__==B.prototype

// Object.create解析
Object.create = function(prototype){
  function Middle(){
    // 没有this.constructor = xx
  }
  Middle.prototype = prototype
  return new Middle()
}
A.prototype = Object.create(B.prototype)
A.prototype.constructor = A
// A.prototype.__proto__ === B.prototype

类型注解

基础类型

  • number
  • string
  • boolean
  • symbol
  • undefined
  • null tips:
  1. strictNullChecks未开启时,null,undefined可以赋值给所有类型,除了never

其他类型

  • any, 所有类型的父类型,所有类型的子类型,可以调用任何方法属性,跳过检查
  • unkown,所有类型的父类型,但不是所有类型的子类型,不能调任意属性
  • never,一个在逻辑上不会出现的类型
  • enum, 如果值时数字类型,则会双向映射,否则不会
  • void
// never
function fn():never{ throw new Error() }
function fn(param: string){
  if(typeof param === 'string'){
    // param被推断为string
  }else{
    // param被推断为never
  }
}

// 枚举
enum Status {
  on = 0,
  off
}
// 编译为,数字值类型双向映射
const Status={}
Status[Status[0]='on']=0

引用类型

  • object
  • {} , 等价于Object类型 ,只是没有Object类型的方法
  • 数组
  • 元组
  • 函数类型
// 元组
// 标签
type ArrType = [name:string, age:number]
// 可变元组
type ArrType = [name:string, age:number,...rest:any[], stu:string]
// 默认推导
const arr = [1 , '2'] //被推导成 string|number[] ,而不是元组
// 函数类型定义的几种方式
function fn(param: string):string{}
type Fn = (param: string)=> string
interface Fn{
  (param: string):string
}

// 方法定义
abstract class List2 {
  abstract add(param: string):string
  put(param:string):string{ return '' }
}
type Tp = {
  fn(param:any):string
}
interface Tp2{
  fn(param:any):string
}

装箱类型

  • Object
  • String
  • Function
  • Boolean
  • Number
  • Symbol

tip

  1. 装箱类型接受的值,可以接受所有的拆箱类型的值,而且比拆箱类型多了些方法属性的定义
  2. object只能接收对象或null ; 而Object类型还可接收基础类型

字面量类型(值类型)

只读

  1. const arr = [1,2] as const //子元素不能赋值,as const后变成==值类型==了
  2. let arr : readonly string[] = ['']

类型断言(类型转换)

两种语法

  • o as any
  • o (tsx|jsx不能用)

什么情况下可以类型断言

  1. any 和 unkown是所有类型的父类,和任何类型都可以相互断言
  2. 类implements接口,类extends父类 , 父子类可以相互断言(对于类,接口,type定义的接口)
  3. 没有继承关系,就是鸭子变形法,判断一个是不是另一个的子集(可选属性忽略,不进行判断;==对于类,不能两个类,同时有private,protected修饰符,只允许一个类有==),如果是则可以相互断言
  4. 联合类型A|B, 和其子类型A或B可以相互断言 ,3值类型属于number子类型,也就属于number|string的子类型
  5. 交叉类型 ,T & U是T & U & V 的子集,可以相互断言
  6. 空对象值类型{},是任何对象类型的子类型,可以相互断言
// 联合类型
const a: 3 = 3;
type X = string | number;
(a as X).toString()

// 可选属性忽略
type A = {
  a: string;
  b?: number; // 可选属性忽略
};
type B = {
  a: string;
  c: number;
};
const a: A = { a:''};
(a as B).c

// 类修饰符private ,protected
// 不能两个类,同时有private,protected修饰符,只允许一个类有,这样才是一个类是另一个类的子集,因为private和protected分别只能在自己类或子类中使用
class A {
  constructor(public a: string, private b: number) {}
}

class B {
  constructor(public a: string) {}
}
const a = new A('', 12);
(a as B).a;

类型守卫

内置的类型守卫

  • typeof
  • instanceof
  • in
  • == , === , != ,!==
function fn(o: string | { b: string } | { a: number }) {
  if (typeof o === 'string') {
    o.charAt(0); // string类型
  } else if ('a' in o) {
    o.a; // {a:number}类型
  } else {
    o.b; //{b:string}类型
  }
}

自定义类型守卫

type A ={a:string}
function isA(val: any): val is A{
   return val?.a !== undefined
   // 返回true ,则val is A
}
function test(val: any) {
   if(isA(val)){ // 如果isA(val)是true才会进下面的代码块
      val.a
     // 推断为A类型, isA()返回true所进入的代码块中,
     // val都会被类型断言为A
   }
}

Tip:

  1. 类型守卫会自动推到并缩小类型
  2. 自定义守卫是另一种形式的类型断言

泛型

概念

  • 类似函数形参,使用时传入类型具体化
  • 泛型约束extends, T extends number
  • 泛型默认值 List<T = any> ,如果没写默认就是unkown
  • keyof,获取一个类,接口或对象类型的属性key(包括原型上的方法),组成的【联合类型】
  • typeof, 运行时,返回的js类型 ; 编译时,如type A =typeof '12' // 获取'12'的类型,string, 赋值给A, 那么A就是string
type OT = {
  a : sting ;
  b : number;
}
class A<T extends keyof OT , K extends object>{
   t:T // T extends 'a' | 'b'
}

type S = keyof 'ad' // S = 'slice' | 'charAt' | ... //原型上的属性方法也算

type Atype = typeof {a : 1} // Atype = {a:number}

泛型接口

interface List<T = any>{
   add(element:T):number
}

泛型类

class Clist<T> implements List<T> {
    add(element:T){return 12}
}
const clist = new Clist<string>()

泛型函数

  1. 函数调用的编译检查,==外部获取泛型函数调用的返回值根据定义(如果有定义)的返回类型决定==,而不管函数内部的实际返回
  2. 函数内部的编译检查,是假设泛型T一开始就是某种类型,如果函数的返回值类型为T,那么函数内部的所有返回类型智能是T或any , any[]也不行,但any[]可以赋值给T[]
function fn<T>(a: T): T[] {
  return a as any; // 函数内部的as any ,有时可用于解决一些编译错误
}

const a =fn(12) 
// a被判定是number[],但实际运行返回12
a.concat([12])

函数方法重载

概念

  1. 重载标签,调用时,调用的是重载标签的类型,然后进入函数体
  2. 实现标签,要兼容所有重载标签的类型
  3. 函数体,==函数体里的参数变量取的是实现标签的形参及默认值==

注意点

  1. 实现标签的参数只能比重载标签的参数少或者一样,==参数及其默认值是从实现标签里取的,重载标签里写参数默认值没有意义,而且重载标签里的参数名可以任意取==

普通

class List{
  arr:object[]
  // 方法
  get(index:number):object
  get(element:object):number
  get(param:number|object):any{
     if(typeof param === 'number'){
        return this.arr[param]
     }else{
        return this.arr.indexOf(param)
     }
  }
} 

// 函数类似

类型定义
// 接口定义的函数重载
interface Fn{
  (index:number):object
  (element:object):number
}
function fn(index:number):object;
function fn(element:object):number;
function fn(param:any = 12 ):any{
  if(typeof param === 'number'){
     return {}
  }else {
     return 12
  }
}

const xx: Fn = fn
const a = xx(12) // a是object类型
const b = xx({}) // a是number类型

泛型函数重载

// 其他两种定义方式
type Fn2<T> = {
  (index: number): number;
  (element: T): T[];
};
interface Fn1 {
  (index: number): number;
  <T>(element: T): T[];
}
// 
interface Fn<T> {
  (index: number): number;
  (element: T): T[];
}
function fn(index: number): number;
function fn<T>(element: T): T[];
function fn(param: any): any {
  if (typeof param === 'number') {
    return param;
  } else {
    return [param];
  }
}

const xx: Fn<string> = fn;
// 函数调用后的返回类型,并不是根据函数体内的返回类型决定的,而是重载标签的返回类型决定的,类似类型断言
const a = xx(12); // a是number类型
const b = xx('123'); // a是string[]类型

构造器函数类型

概念

  1. 类变量自己的类型,typeof Aclass叫构造函数类型
  2. ts定义通用的构造器函数类型 type ConstructorType = {new (...args:any[]) : any }, 或者interface ConstructorType{ new (...args:any[]) : any }
  3. typeof Aclass 是ConstructorType子类

泛型构造器函数类型

type ConstructorType<T> = new (...args:any[]) => T
type ConstructorType<T> = { new (...args:any[]) : T } // 接口方式

工厂函数

type ConstructorType<T> = new (...args:any[]) => T
function createInstance<T>(tarClass: ConstructorType<T>):T{
   return new tarClass()
}
// 调用
class A{ 
  a = 12
}
const ins = createInstance(A)

交叉类型

概念

  1. 将多个类型合并(属性方法的==并集==,而不是交集)
  2. T & U是T & U & V 的子集,可以相互断言
  3. 合并过程是,遍历所有的key,每个相同的key进行类型合并,取类型交集,取不到就是never类型
// 例子1,同一个key,如果相同类型的,可选属性y会被非可选属性y覆盖
type X = {y?:number,z:string} & {y:number,x:string} 
// {z:string,y:number,x:string} 

// 例子2,同一个key,不同类型的,交叉并集后变成never类型,这样会没法赋值
type X = {y:string, z:string} & {y:number,x:string} // {y:never,z:string,x:string}

// 例子3,同一个key,不同类型的(但是一个是另一个的子类型的,子类型的可以赋值)
type X = {y: 1, z:string} & {y:number,x:string}  // {y:1,z:string,x:string}
// y, 值类型1是number子类型,y可以赋值成1

联合类型

概念

  1. 或运算逻辑|
  2. ==对于赋值,对象的联合类型,可以接受联合类型中任意一种数据类型全部属性和方法,也可以是两个类型的全部属性和全部方法【可选属性和方法除外】,也可以是一种数据类型全部属性和方法+其他类型的某个属性和某个方法==
// 相当于取一个类型为子集,然后其他类型依次处理的所有key变为可选属性(同key则忽略)
type X = {a:number,d:number} | {a:string,b:number} 
// {a:number,d:number,b?:number} 或 {a:string,b:number,d?number}

// 例子
type X = { a: number; d: number } | { a: string; b: number } | { b: string; x: number };
const x: X = { a: '',, d: 1,  b: 12, x: 1 }; // {a,d}为一子集,其他皆各个类型的属性,

infer

概念

  1. 占位符式修饰数据类型的关键字,被修饰的数据类型要等到使用时才被推断出来(三元表达式)

三个场景

  1. extends条件语句后的函数类型的参数位置
  2. extends条件语句的返回值位置
  3. 类型泛型具体化的类型上
// 场景1
class Customer{}
type CustFunc = (cust:Customer)=>string
type inferType<T> = T extends (param: infer P) => any ? P : T

type X = inferType<CustFunc> // p = Customer ,如果不符合,则返回T类型
const x: X = new Customer()

// 场景2
type inferType2<T> = T extends (param: any)=>infer P ? P : T 
type X2 = inferType2<CustFunc> // p = string类型
const x2: X2 = ''

// 场景3
type inferType3<T> = T extends Set<infer P> ? P : never
type X3 = inferType3<Set<string>> // P = string 
const x3:X3 = ''

infer获取函数参数类型

(通过构造器函数类型匹配三元表达式条件)

type ConstructorParamterType<T extends new(...args:any[]) => any> = 
T extends new(...args : infer P) => any ? P : never

type Constructor<T> = new (...args:any[])=>T
function createInstance<T , CP extends new (...args:any[])=>any>(targetClass: Constructor<T>, ...args:ConstructorParamterType<CP>){
   return new targetClass(...args)
}

// 调用
class Test{
 constructor(public age:number,readonly name:string){}
}
const ins=createInstance<Test, typeof Test>(Test,12,'名字') 
//第一个Test是类型,第二个Test是类变量

高级类型

Extract(提取)

  1. 定义 type Extract<T,U>= T extends U ? T : never
  2. extends和类型断言的区别,(1)子类extends父类,为true (2)父类extends子类,一般为false,如果子类和父类的属性一样,为true
// 联合类型
type X1 = Extract< string|number|symbol, string|number > // string|number
// 前者会拿所有的类型一一跟后者的(所有)类型比较是否extends, 如果extends成立,则添加前者的该类型,不成立返回never,则忽略

// 函数
// 判断:函数的返回值的类型必须一致,参数少的extends参数多的
// 例子
type Func1 = (one:number, two:string) => string
type Func2 = (one:number) => string
type X2= Func1 extends Func2 ? Func1 : never // never
type X3 = Extract<Func1, Func2> // never
type X4 = Extract<Func2,Func1> // Func2

// 对象和接口,属性多的extends属性少的,鸭子辨形法,前者符合后者的所有属性即可
type X5 = Extract<{a:string}, {a:string;b:number}> // extends false , never
type X6 = Extract<{a:string; b:number}, {a:string}> // true , {a:string; b:number}

// 一个实际应用的技巧
// T extends object 可改写为类型Extract<T,object> ,如果不是object类型,会返回never,那肯定赋值错误

Exclude (排除)

  1. 定义 type Exclude<T, U> = T extends U ? never : T ,和Extract相反
// 联合类型
type X1 = Exclude< string|number|symbol, string|number > // symbol

// 函数,略
// 对象接口
type X5 = Exclude<{a:string}, {a:string;b:number}> // extends false , {a:string}
type X6 = Exclude<{a:string; b:number}, {a:string}> // true , never

Record<K,V>

  1. 定义
  2. P[K]
  3. ts规定, typeof any 等价于 string|number|symbol
  4. K in keyof T
  5. K in T
// Record
type Record<K extends any , T> = { 
   [P in K]: T 
}

// 1. P[K]
type M = {a:string}
type A = M['a'] // string

// 2. 
let x: Record<number,number> = [1] //数组的索引是数字

// 3. keyof any
2 extends keyof any  
// 2 extends keyof string|number|symbol 成立 
// 2 extends string

// 4. K in keyof T
keyof {a,b,c} // 'a'|'b'|'c'

// 5. K in T 
[P in string] 表示任意一个字符串

// 6. object 和 Map
let o:object ={} ; 
o.a //报错没有该属性
o['a'] //不报错

Pick(选取)

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

type O ={a:string;b:number;c:boolean}
type PO= Pick<O, 'a'|'b'> // {a:string;b:number}

Partial(变可选)

type Partial<T> = {
  [P in keyof T]? : T[P]
}

Required(可选变要求有,和Partial相反)

type Required<T> = {
  [P in keyof T]-? : T[P]
  // 如果没有-?,则无任何改变,可选属性依旧是可选属性
}

ReadOnly (只读)

type ReadOnly<T> = {
  readonly[P in keyof T]: T[p]
}

Omit(省略)(与pick相反,排出)

type Omit<T, K extends keyof T> 
 = Pick< T, exclude<keyof T , K> >

类型声明文件

涉及语法

  1. declare var/const/let 声明全局变量
  2. declare function
  3. declare class
  4. declare enum
  5. declare namspace 声明(含有子属性)的全局对象
  6. interface 和 type 声明全局类型
  7. export 导出变量
  8. export namespace // 导出(含有子属性)的全局变量
  9. export default // ES6默认导出
  10. export = // commonjs 导出模块
  11. declare global 扩展全局变量
  12. declare module 扩展模块
  13. /// 三斜线指令

不同场景

1. 全局变量

场景:通过script标签引入的第三方库,注入全局变量 处理:在项目目录中新建global.d.ts ,或者 Jquery.d.ts等,只要能被ts编译 注意:

  1. declare namspace里头的无需在declare
  2. interface ,type无需declare
  3. interface定义在namespace里头避免全局污染,命名冲突
  4. 声明合并
  5. 三斜线指令,引入其他文件定义的类型(因为是全局声明,而非模块声明,不能用import ,只能用三斜线指令)
  6. 只定义类型,不能实现
// Jquery.d.ts
declare function jQuery(selector:string): any
declare function jQuery(domReadyCallback: () => any): any

declare namespace jQuery {
    interface AjaxSettings {} 
    function ajax(url: string, settings?: any): void;
    // 嵌套namespace
    namespace $ { 
       fn():any
    }
}

// index.d.ts

/// <reference types="jquery" />
declare function fetch(options:jQuery.AjaxSettings):string
declare enum Status {
   on,
   off
}
declare var window:any
declare class Image{
   src: string
   getWidth():number
}

2. npm包

import foo from 'foo'

2.1.方式:

  1. @types/foo,写好声明文件,发布@types
  2. (推荐)和npm包绑定在一起,查找声明文件顺序:(1)package.json中的字段types或typings,(2)index.d.ts
  3. 专门指定一个目录types,用来管理声明文件, tsconfig.json需要配置,告诉tsu去哪里找模块 声明文件
// tsconfig.json
{
  "compileOptions":{
     paths:{
       "*":["types/*"]
     }
  }
}
  1. xx.js和xx.d.ts共存的情况下,vscode找寻模块会优先找d.ts
  2. declare module 'foo' {} // 全局声明模块,声明合并已有的模块声明

2.2 导出

  1. export 导出变量
  2. export namespace 导出对象
  3. export default // ES6默认导出
  4. export = // commonjs导出模块,相当于module.exports = xx

export 在npm包的声明文件中,declare不再会声明一个全局变量,只有导出的变量,才能被import引入

// 方式1
export const name:string
export class A {}
export interface X {} 

// 方式2
declare const name:string
declare class A {}
declare interface X {}
export {name, A, X} 

// 使用
import {name,A,X} from 'foo'

export namespace 导出一个拥有子属性的对象

export namespace foo{
   function fn():void
}

// 使用
import {foo} from 'foo'
foo.fn()

export default 默认导出: 只有function ,class ,interface可以默认导出,其他变量需要闲定义出来,再默认导出

// 声明
declare const name:string
export { name }
export default function foo():string 

import foo , {name} from 'foo'
foo()

export = 类似module.exports = ;用了export=后,就不能再单个导出了export {}

export = foo 
declare function foo():string

import foo from 'foo'
const foo = require('foo')

export as namespace 用与umd库,支持导出为全局变量那种;一般是先有了包声明文件,再基于它添加一条export as namespace xx

export as namespace foo
export = foo
export default foo
declare function foo():string
declare namespace foo{

}

扩展全局变量

interface Window{
   say():void
}
// 扩展已有的明名空间
declare namespace JQuery {
   xx():void
}

npm包货umd库中扩展全局变量

declare global {
   interface Window{
      say():void
   }
}
export {}

模块声明declare module

场景:一个模块扩展了一个模块插件,但类型声明文件只有原来模块部分,没有模块插件部分,这时候可以用declare module 扩展原来模块,补充模块插件的类型声明

声明的模块内部,要将导出的内容export才行

// types/moment-plugin/index.d.ts
import * as moment form 'moment'
declare module 'moment' {
   export pluginFn(): moment.CalendatKey // 模块需要export
   export interface xx {}
}
// 使用
import * as moment from 'moment'
import 'moment-plugin'
moment.pluginFn()

自动生成类型声明文件

tsconfig.json 里 compilerOptions.declaration =true; 会每个文件模块生成npm类型声明文件(去掉实现)

声明合并

  1. 接口,同属性不同类型会报错
  2. 方法,合并后类似重载
interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}
//相当于
interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}

// 如果一个price是string,另一个是number ,那么会报错