ts下篇

155 阅读9分钟
联合类型、交叉类型
  1. 交叉类型的问题:两个类型中有相同的属性且类型不同时,&集后的结果是never
// 两个类型中有相同的属性且类型不同时,&集后的结果是never
interface Person1 {
  handsome:string,
  gender:number
  meta:{
      n:number
  }
}
interface Person2 {
  high:string,
  gender:string
  meta:{
      n:string
  }
}
type Person3 = Person1 & Person2
type IGender = Person3['meta']['n'];
  1. 交叉类型可以快速扩展属性
let obj = {name:'jw',age:30}
let person:{name:string,age:number,address:string} = obj as typeof obj & {address:string}
extends 条件类型

子类型 extends 父类型 = true

  1. never是任何类型的子类型 最small 嘻嘻
  2. never类型 - 字面量类型 -基本类型 -包装类型 <-any unknow <- obejct/Object/{}
type T1 = never extends "str" ? true : false;// true
type T2 = "str" extends string ? true : false;// true
type T3 = string extends String ? true : false;// true
// 万物皆对象
type T4 = String extends object ? true : false;
type T5 = string extends {} ? true : false; // true

type T6 = string extends object ? true : false; // false  
// 基本类型 extends 对象类型 ???去你的吧 又看类型又看结构,类型不对直接false 马德

//再看看这个
// objext {} Obejct 这个三个怎么相互extends都是true 马德一伙儿得
  1. obejct/Object/{}的区别
    • obejct 只针对对象类型 String、Number、Function {} extents obejct | object extends {} {}和obejct可以看成字面量类型/结构
    • Object extends object 从结构看的,所以返回true
    • Object extends {} 从结构看的,所以返回true
分发机制(条件类型)
  1. any自带分发机制
    • 条件类型是有分发机制的 1 + 除了1的部分 第一次用1和1比返回true(如果不是true而且其他值则返回对应的值)
    • 第二次用非1的值和1去比返回false
    • 两次结果作为联合类型返回 true|false = boolean类型
type PaoPao = any extends 1 ? true : false // boolean ???凭什么
  1. never通过泛型传入,只返回never
type t<T> = T extends 1 ? true : false
type t2 = t<never> // t2:never 气昏了 吗德
  1. 通过条件类型 来进行类型的区分,条件语句也可以实现约束的效果
    • 分发触发的机制:
    • 1.联合类型通过泛型传递 type xxx = res<Bird | Fish>
    • 2.比较的时候(extends)产生的分发 type res<T extends Bird | Fish>
    • 3.类型需要是裸类型 泛型

裸类型又是什么???

  • 裸类型就是泛型T,一个人用不跟别人搭配。 那又如何搭配呢?? 教教我
  • 方式一: T & {} extends Bird ? Sky : Water 跟{}交个朋友 经典解决分发
  • 方式二: T[] extends Bird[] ? Sky : Water
type t3 = 100 extends 100 | 200 ? true : false

interface Bird {
  name: '鸟'
}
interface Water {
  name: '水'
}
interface Fish {
  name: '鱼'
}
interface Sky {
  name: '天空'
}

// type res<T extends Bird | Fish> = T extends Bird ? Sky : Water  会产生分发,如何解决呢?如下: T & {}
type res<T extends Bird | Fish> = T & {} extends Bird ? Sky : Water
type t4 = res<Bird | Fish>
  1. 分发的场景
type NoDistribute<T> = T & {};

// 分发机制有的场景需要,有的场景需要禁用, 不能一概而论。
type UnionAssets<T, K> = NoDistribute<T> extends K ? true : false;
type U1 = UnionAssets<1 | 2, 1 | 2 | 3>; // true
/* 
  1 比 1|2 // true
  2 比 1|2// true
  3 比 1|2// false
  true | false  联合一下 = boolean
*/
type U2 = UnionAssets<1 | 2 | 3, 1 | 2>; // boolean

// 判断两个类型是否完全一致 ?   1|2    1|2
type isEqual<T, K, S, F> = NoDistribute<T> extends K
  ? NoDistribute<K> extends T
    ? S
    : F
  : F;
type A2 = isEqual<1 | 2, 1 | 2, true, false>;
/* 
  1 extends 1|2 ? 1|2 extends 1 ? true : false : false  分发后值就被确定了拿去对比就出问题啦。。。
*/
type FormatVal<T> = T extends string
  ? string
  : T extends number
  ? number
  : never;

// 映射关系 可以考虑用泛型,参数个数不一致,类型和入参数无法,考虑重载
function sum<T extends string | number>(a: T, b: T): FormatVal<T> {
  return a + (b as any)
}
let res = sum(1, 2)
内置类型

内置类型中有很多类型是基于条件类型的

  1. Extract 左边 包含 右边的值有哪些
  2. Exclude 左边 不包含 右边的值有哪些
  3. NonNullable 不为空的类型返回,否则返回never -T & {} 因为空对象不包含null和undefined,所以就被干掉了。
type Extract<T, U> = T extends U ? T : never; // 实现原理
type ExtractRes = Extract<1 | 2 | 3 | 4, 1 | 2 | 3| 5>; // 1|2|3
type Exclude<T, U> = T extends U ? never : T;  // 实现原理
type ExcludeRes = Exclude<1 | 2 | 3 | 4, 1 | 2>;

const ele = document.getElementById("app");
// type NonNullable<T> = T extends null | undefined ? never : T;
// type NonNullable<T> = T & {};  // 实现原理 
type Ele = NonNullable<typeof ele>;

除了基于条件类型之外,我门还有基于对象类型

  1. Compute 合并两个interface
interface Person {
  name: string;
  age: number;
}
interface Person2 {
  name2: string;
  age2: number;
  sex: string;
}
type Compute<T> = {
  [key in keyof T]?: T[key] // 加了? = partial的实现 全部可选
}
type p = Compute<Person & Person2>
  1. Partial 设置全部key为可选的 深度可选的自己实现 type r5 = DeepPartial<Compny>
  2. Required全部必填 -? 深度必填的自己实现
  3. Readonly全部只读 深度必填的自己实现
type DeepPartial<T> = {
  [key in keyof T]?: T[key] extends object ? DeepPartial<T[key]> : T[key]
}

interface Person {
  name: string;
  age: number;
}
interface Compny {
  name: string;
  address: string;
  person: Person;
}
type r5 = DeepPartial<Compny>

const test: r5 = {
  name: 'paopao',
  person: {}
}
type Required<T> = {
  [key in keyof T]-?: T[key] extends object ? Required<T[key]> : T[key]
}

type Readonly<T> = {
  +readonly [K in keyof T]: T[K];
};
type ReadonlyRes = Readonly<IPerson>;

// 不能修改就是readonly , 却掉readonly
type Mutate<T> = {
  -readonly [K in keyof T]: T[K];
};
type MutateRes = Mutate<ReadonlyRes>;
  1. Omit 挑选出需要的属性返回
  2. Pick 删除不需要属性,保留剩下的返回
type Pick<T, K extends keyof T> = {
  [key in K]: T[key];
};
type PickRes = Pick<IPerson, "company" | "age">;
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type OmitRes = Omit<IPerson, "name" | "age">;
类型推断 infer、inference
  1. infer关键字只能在条件类型中使用,用来提取类型某一个部分的类型 比如函数的参数/返回值
  2. ReturnType 获取返回值类型
  3. Parameters 获取参数类型
  4. InstanceType 获取类的实例类型
  5. ConstructorParameters 获取构造函数的参数类型
// ReturnType
type ReturnType<T> = T extends (...args: any[])=> infer R ? R : never
type r = ReturnType<typeof foo>

type Parameters<T> = T extends (...args: infer R)=> any ? R : never
type p = Parameters<typeof foo>

class Person {
  constructor(a: string, b: string) {}
}
// type ConstructorParameters<T extends { new (...args: any[]): any }> =
//   T extends { new (...args: infer P): any } ? P : any;

type ClassConstructorParameters = ConstructorParameters<typeof Person>;

// type InstanceType<T extends { new (...args: any[]): any }> = T extends {
//   new (...args: any[]): infer R;
// }
//   ? R
//   : any;
type ClassInstanceType = InstanceType<typeof Person>;

// 如何讲一个元组转化成联合和类型

type ITumple = [string, number, boolean]; // string | number | boolean
// type TumpleToUniom = ITumple[number];

type ElementOf<T extends any[]> = T extends Array<infer R> ? R : never;
type TumpleToUniom = ElementOf<ITumple>;

// 对元组进行参数移动, 将元组头尾进行交换 . infer 要配合 extends
type SwapHeadTail<T extends any[]> = T extends [
  infer Head,
  ...infer Body,
  infer Tail
]
  ? [Tail, ...Body, Head]
  : never;

type Res = SwapHeadTail<["jw", 1, 2, 3, 4, 5, 30]>;

  1. Promise 的递归
// infer 可以递归推断
type PromiseVal<T> = T extends Promise<infer R> ? PromiseVal<R> : T;
type PromiseReturnVal = PromiseVal<Promise<Promise<200>>>;
  1. Record 描述对象类型 取代object
// 推导出类型 (根据泛型的位置 来推导具体的类型)
function map<T extends keyof any, K, R>(
  obj: Record<T, K>,
  callback: (value: K, key: T) => R
) {
  let result = {} as Record<T, R>;
  for (let key in obj) {
    result[key] = callback(obj[key], key);
  }
  return result;
}
map({ name: "jw", age: 30 }, (value, key) => {
  return true;
});
类型声明

ts兼容性分成两种 子 extends 父 结构来考虑,以下是第一种

  1. 字符串从结构类型
let str: string = "abc"; // 类型层级
let obj!: { toString(): string };

obj = str; // 结构来考虑   extends object   extends {}
  1. 函数兼容性 (参数和返回值的兼容性)
    • 对于函数而言他的兼容性, 少的可以赋予给多的, 参数少的是子类型
    • 返回值要求安全, 返回值要求是子类型.
let sum1 = (a: number, b: number): string | number => a + b;
let sum2 = (a: number): number => a;

type Sum1 = typeof sum1;
type Sum2 = typeof sum2;

type X = Sum2 extends Sum1 ? true : false;

const forEach = <T>(
  arr: T[],
  callback: (val: T, key: number) => string | number
) => {
  for (let i = 0; i < arr.length; i++) {
    let r = callback(arr[i], i); // 调用函数的时候 会传递多个参数
  }
};
forEach(["A", 2, 3, {}], function (val) {
  return "abc";
});
  1. 类的兼容性 也是一样 比较的是实例
    • 如果类中的属性 有private 或者protected则两个值不能互相复制
class A {
  private a = 1;
}
class B {
  private a = 1;
}
const b: B = new A();

ts中从标称类型来看

// 希望给基本类型做区分,达到差异化的目的

type withType<T, K> = T & [K];
type BTC = withType<number, "BTC">;
type USDT = withType<number, "USDT">; // 基于内置类型来做构建

const c1 = 100 as BTC;
const c2 = 100 as USDT;
function money(val: BTC) {}
money(c1);
逆变协变
  1. 逆变:在函数中可以标记儿子,但是传父亲
    • 内部调用函数的时候 可以传递 Child 和 Grandson. 但是在使用属性时 只能认为最多就是child
  2. 协变:在函数中可以标记父亲,返回的是儿子
    • 函数的返回值, 需要返回子类,因为内部代码在访问属性的时候要保证可以访问到
class Parent {
  car() {}
}
class Child extends Parent {
  house() {}
}
class Grandson extends Child {
  sleep() {}
}
// 安全性考虑
// 1) 内部调用函数的时候 可以传递 Child 和 Grandson. 但是在使用属性时 只能认为最多就是child
// 2) 函数的返回值, 需要返回子类,因为内部代码在访问属性的时候要保证可以访问到
function fn(callback: (ctr: Child) => Child) {
  // 我交给回调的有房、有车
  let r = callback(new Child());
  r.house;
}
fn((child: Parent): Grandson => {
  return new Grandson();
});
type Arg<T> = (arg: T) => void;
type Return<T> = (arg: any) => T;
type ArgReturn = Arg<Parent> extends Arg<Child> ? true : false; // 基于函数参数的逆变
type ReturnReturn = Return<Grandson> extends Return<Child> ? true : false; // 返回值是协变的

// 逆变带来的问题(我们写业务的时候 还是要正常开启逆变)
interface MyArray<T> {
  //   concat: (...args: T[]) => T[];
  concat(...args: T[]): T[]; // 这种写法不进行逆变检测,所有在描述对象中的方法时全部采用这种方式
}
// parent: (...args: Parent[]) => Parent[];
// child:  (...args: Child[]) => Child[];
// 将child 赋予给parent   传父返儿子
let parentArr!: MyArray<Parent>;
let childArr!: MyArray<Child>;

// chilldArr 能不能赋予给 parentArr

// [{car(){}}] = [{car(){},house(){}}]

parentArr = childArr;
类型兼容性
  1. 两个枚举之间 不能兼容
enum E1 {
  a = 1,
}
enum E2 {
  a = 1,
}
// e2 = e1; //两个枚举之间 不能兼容
  1. 泛型兼容性, 如果生成的结果一致 类型就就兼容
type II<T> = { name?: T };
type X1 = II<string> extends II<string> ? true : false; // 生成结构一致即可
  1. 对象的兼容性, 多的属性可以赋予给少的
  2. 类型层级兼容性,never -> 字面量 -> 基础类型 -> 包装类型 -> any / unknown
  3. 子 extends 父 满足 即可赋值
类型推导的概念
  1. 赋值推断, 根据赋予的值来推断类型
  2. 函数时通过左边来推导右边, 基于上下文类型来进行自动的推导
  3. 函数返回值编辑成void ,赋予一个函数的时候,意味着不关心返回值
let name = "jw";
let age = 30; // 1.
// 2.
const sum: (a: string) => void = (a) => {
  return a;
};
let r = sum("1");

function forEach1(arr: number[], callback: () => void) {
  // 这里的void 不关心返回值 3.
  callback();
}
forEach1([1, 2, 3], function () {
  return [];
});
类型保护
  1. 基于js + ts (收窄)
  2. ts很多情况下 需要使用联合类型, 默认情况下只能使用公共的方法,识别类型 (针对某个类型进行处理)
  3. typeof(基础类型) instanceof(类类型) in(接口类型,可辨识类型)
function fn2(a: string | number) {
  if (typeof a === "string") {
    a; // string
  } else {
    a; // number
  }
}
class Cat {
  cry() {}
}
class Dog {
  eat() {}
}
function getInstance(clazz: { new (...args: any[]): Cat | Dog }) {
  return new clazz();
}
const instance = getInstance(Cat);
if (instance instanceof Cat) {
  instance.cry;
} else {
  instance.eat;
}

interface Bird {
  kind: "鸟";
  fly: string;
}
interface Fish {
  kind: "鱼";
  swim: string;
}
// 可辨识类型  通过in来实现
function getAimal(val: Bird | Fish) {
  // 基于差异化来辨别
  if ("fly" in val) {
    val;
  } else {
    val;
  }
  if (val.kind == "鸟") {
    val.fly;
  } else {
    val.swim;
  }
  1. 函数的嵌套不识别的问题 ? ! if 都有缩小范围的用途 (基于上下文类型的推导,会因为作用域的变化 而产生问题)
function addType(val?: number) {
  // 断言就是自己说的算,出错了自己承担
  val = val || 0;
  return function (type: string) {
    return type + (val as number).toFixed(); // ts 无法识别的时候 需要用断言
  };
}
addType(100)("$");
  1. is 语法 ts语法 主要在辅助的方法中用的比较多
// is 语法 ts语法  主要在辅助的方法中用的比较多

interface Bird {
  kind: "鸟";
  fly: string;
}
interface Fish {
  kind: "鱼";
  swim: string;
}
function isBird(val: Bird | Fish): val is Bird {
  // ts的返回值类型
  // 函数的名字 和返回值是无关的
  // true是bird 还是false 是bird
  return "fly" in val;
}
function getAimal(val: Bird | Fish) {
  // 基于差异化来辨别
  if (isBird(val)) {
    val;
  } else {
    val;
  }
}

unknown冷知识

  1. unknown 和 any 都是顶级的类型
  2. unknown 是无法识别的类型 没有key
  3. any不校验 意味着可以调用 可以取值,unknown 是any的安全类型
  4. 如果标识为unknown 类型 必须先类型保护再去使用 (收窄类型 在使用)
type keys1 = keyof any;
type keys2 = keyof unknown; //unknown 是无法识别的类型 没有key

type unionUnknown = unknown | string | true | false; // unknown 任何类型都可以赋予给unknown
type interUnknown = unknown & string; // string
function isNumber(val: unknown): val is number {
  return typeof val === "number";
}
function isString(val: unknown): val is string {
  return typeof val === "string";
}

if (isNumber(a)) {
  a.toFixed; //
} else if (isString(a)) {
  a.charCodeAt;
}