TypeScript工具类型

类型挑战【转载】:https://wangtunan.github.io/blog/typescript/challenge.html#%E4%BB%8B%E7%BB%8D

工具类型

Partial<Type>(可选)

  • 解释:用来构造 (创建) 一个类型, T 的所有属性设置为可选的

    interface User {
      id: number;
      name: string;
    }
    const obj1: Partial<User> = { id: 25 }  // 正确
    
  • 实现:type Partial<T> = { [P in keyof T]?: T[P]; }

    • [P in keyof T] 通过映射类型,遍历T上的所有属性
    • ?: 语法称为映射修饰符,用于影响可选性。
  • 常用场景一:构造一个新类型

    interface Person {
      name: string;
      age: number;
    }
    type PartialPerson = Partial<Person>
    
  • 常用场景二:获取由对象属性组成的类型

    const obj = {
      id: 1,
      name: 'James',
      salary: 100,
    }
    type Test = Partial<typeof obj>;
    

    必须使用 typeof 类型运算符,因为 Partial 需要一个类型

映射修饰符可以以两种方式影响可选性,可以通过前缀 - 或者 + (即-?:/+?:)删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀

Required<Type>(必选)

  • 解释:声明类型中的每一项都是必需项

    interface User {
      id?: number;
      name?: string;
    }
    
    const obj1: Required<User> = { id: 25 };    // Error 缺少属性
    const obj2: Required<User> = { id: 25, name: '666' }; // 正确
    
  • 实现:type Required<T> = { [P in keyof T]-?: T[P]; }

    • 这里的 -? 就是抵消掉问号 ?

Readonly<Type>(只读)

  • 解释:将 Type 的所有属性都设置为 readonly (只读 ), 构造出来的结构完全相同,但所有属性为只读

    interface User {
      des: string;
    }
    
    // type ReadonlyUser = Readonly<User>
    const todo: Readonly<User> = {
      des: "A student",
    };
    
    todo.des = "Hello"; // Error 类型值不可再赋值改变
    
  • 内部实现:type Readonly<T> = { readonly [P in keyof T]: T[P]; }

    • 主要实现是通过映射遍历所有key,然后给每个key增加一个readonly修饰符

Record<Keys, Type>

  • 构造一个对象类型,其属性键为Keys,属性值为Type。此实用程序可用于将一个类型的属性映射到另一个类型

    interface PageInfo {
      id: number;
      title: string;
      isCache: Boolean
    }
    type Page = "home" | "about" | "login";
    
    type NavType = Record<Page, PageInfo>
    /**
     * =>
     * type NavType ={
     *  home: PageInfo;
     *  about: PageInfo;
     *  login: PageInfo;
     * }
     */
    
    const nav: NavType = {
      home: { id: 1, title: 'home Page', isCache: false },
      about: { id: 1, title: 'home Page', isCache: false },
      login: { id: 1, title: 'home Page', isCache: false },
    }
    
  • 实现:type Record<K extends keyof any, T> = { [P in K]: T; }

    • 核心实现就是遍历K,将值设置为T;

    • 注意的是keyof any得到的是string | number | symbol,原因在于类型key的类型只能为string | number | symbol

Pick<Type, Keys>(挑选)

  • Type 中选取一组属性 Keys(字符串字面值或字符串字面值的并集)来构造一个新类型。

    • 即:获取一个类型中的某些key
    interface User {
      name: string;
      age: number;
      address: string;
    }
    type PickUser = Pick<User, 'age' | 'name'>;
    /**
     * =>
     * type PickUser = {    
     *  age: number;    
     *  name: string;
     * }
     */
    
  • 实现:type Pick<T, K extends keyof T> = { [P in K]: T[P]; }

    • extends 限制了 K 的值必须属于 Type 的属性值(keyof Type)

Omit<Type, Keys>(省略)

  • 通过从Type选取所有属性然后移除Keys(字符串字面值或字符串字面值的并集)来构造类型。

    • 即:移除一个类型的某些key
    interface User {
      name: string;
      age: number;
      address: string;
    }
    type OmitUser = Omit<User, 'address'>;
    /**
     * =>
     * type OmitUser = {
     *  age: number;
     *  name: string;
     * }
     */
    
  • 1.利用Pick实现:type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

    • 从联合类型T中排除K后,剩下的属性构成一个新的类型,即‘我们需要的属性联合’

    • 利用Pick提取需要的Keys组成的类型:Omit = Pick<T, 我们需要的属性联合>

  • 2.利用映射类型实现:type Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }

    • 类似Pick,通过映射类型遍历Exclude<keyof T, K>的属性
    • T[P] 设置类型为原来的类型

Exclude<T, U> (排除)

  • T中剔除可以赋值给U的类型。(两者的差集

  • 实现:type Exclude<T, U> = T extends U ? never : T

  • 遍历 T 中的所有子类型,如果该子类型约束于约束于U(存在于U、兼容于U),则返回never,否则返回该子类型

  • never表示一个不存在的类型,与其它类型联合后是没有never

type T0 = Exclude<"a" | "b" | "c", "a">;
/**
* => type T0 = "b" | "c"
*/

Extract<T, U>(提取)

  • 提取T中可以赋值给U的类型。(两者的交集)

    type Test1 = Extract<"a" | "b" | "c", "a" | "f">;
    // => type Test1 = "a"
    
  • 实现:type Extract<T, U> = T extends U ? T : never;

    • 遍历TT的子类型存在于U,则返回该子类型,否则返回never

NonNullable<Type>(非空)

  • 通过从 Type 中排除 nullundefined 来构造一个类型。

  • 实现:type NonNullable<T> = T & {};

    type T0 = NonNullable<string | number | undefined | null>;
    // => type T0 = string | number
    

Parameters<Type>(参数的类型)

  • 返回由函数参数类型组成的元组类型

  • 实现:type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

    • 首先约束参数T必须是一个函数类型,所以(...args: any) => any替换成Function也是可以的;

    • 具体实现:判断T是否是个函数类型,如果是则使用infer PTS自己推导出函数的参数类型,并将推导结果存到类型P上,否则就返回never

    • 示例:

    type T0 = Parameters<() => string>;
    // => type T0 = []
    
    type T1 = Parameters<(s: string) => void>;
    // => type T1 = [s: string]
    
    type T2 = Parameters<<T>(arg: T) => T>;
    // => type T2 = [arg: unknown]
    
    declare function f1(arg: { a: number; b: string }): void;
    type T3 = Parameters<typeof f1>;
    // => 
    // type T3 = [arg: {
    //     a: number;
    //     b: string;
    // }]
    
    type Eg = Parameters<(arg1: string, arg2: number) => void>;
    // => type Eg = [arg1: string, arg2: number]  // 这是一个元组
    
    function sum(a: number, b: number): number {
      return a + b;
    }
    type SumParamsType = Parameters<typeof sum>;
    // => type SumParamsType = [a: number, b: number]
    

扩展infer

  • 关键词infer的作用是让TS自己推导类型,并将推导结果存储在其参数绑定的类型上。

  • infer只能在extends条件类型上使用,不能在其它地方使用。

  • 扩展: infer实现一个推导数组所有元素的类型:

    /**
     * 约束参数T为数组类型,
     * 判断T是否为数组,如果是数组类型则推导数组元素的类型
     */
    type FalttenArray<T extends Array<any>> = T extends Array<infer P> ? P : never;
    
    type Eg1 = FalttenArray<[number, string]>
    // => type Eg1 = number | string;
    
    type Eg2 = FalttenArray<[1, 'asd']>
    // => type Eg2 = 1 | "asd"
    

ReturnType<Type>(返回值类型)

  • 获取函数的返回值类型

  • 实现:type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

    • Parameters<Type>相比,ReturnType<Type>只是将infer R从参数位置移到返回值位置,此时R表示待推断的返回值类型
  • 示例:

    type Func = () => { a: number; b: string };
    type Test = ReturnType<Func>;
    // =>
    // type Test = {
    //   a: number;
    //   b: string;
    // }
    
    type T0 = ReturnType<() => string>;         // type T0 = string
    type T1 = ReturnType<(s: string) => void>;  // type T1 = void
    type T2 = ReturnType<<T>() => T>;           // type T2 = unknown
    
    type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
    // type T3 = number[]
    

    非法的例子:均不满足(...args: any): anytype T 将被视为any处理。

    type T = ReturnType<string>;    // Error
    type T = ReturnType<Function>;  // Error
    

ConstructorParameters<Type>(获取构造函数参数类型)

  • 可以获取类的构造函数的参数类型,存在一个元组中。(如果type不是函数,则为never类型)。

  • 实现:type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never

    • 核心还是用infer推导构造函数的参数类型

    • 首先约束参数T是拥有构造函数的类,判断T是满足约束的类时,利用infer P自动推导构造函数的参数类型,并最终返回该类型。

    • new (...args: any)是构造签名,new (...args: any) => any是构造函数类型字面量

  • 示例:

    type Test = ConstructorParameters<(new (name: string) => any) | (new (age: number) => any)>;
    // => type Test = [name: string] | [age: number]
    
    class Person {
      name: string;
      age: number;
    
      constructor( name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    }
    type PersonParamsType = ConstructorParameters<typeof Person>
    // => type PersonParamsType = [name: string, age: number]
    

扩展1:为什么要对T约束为抽象类(abstract

abstract用来定义抽象类以及抽象类中的抽象方法

// 普通类
class Test {}
// 抽象类
abstract class TestAbst {}

const T1: typeof Test = Test      // 可以赋值
const T2: typeof Test = TestAbst  // Error: 无法将 抽象构造函数类型 分配给 非抽象构造函数类型。

const TAbs1: typeof TestAbst = Test      // 可以赋值
const TAbs2: typeof TestAbst = TestAbst  // 可以赋值
  • 可以将抽象类(抽象构造函数)赋值给抽象类或者普通类,反之不行。

扩展2:关于类类型的表示

如上const T1: typeof Test = Test,虽然用typeof表示类的类型非常方便(其实它不是用来干这个的, 至少不全是),但不能因每次要写一个类类型时都先用class关键词定义一个类。

替代方法:使用构造函数表示class的类型

type PClass = new (name: string, age: number) => { name: string, age: number };

class Person {
  name: string;
  age: number;

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

const TestPerson: PClass = Person

也可以在接口interface中使用:

interface PInter {
  new (name: string, age: number): { name: string, age: number }
}

const TestPerson2: PInter = Person

InstanceType<Type>(获取构造函数返回值的类型)

InstanceType用于获取类的实例化类型,即 new 类 的产物,可以说是执行构造函数的返回值,与上面的ConstructorParameters类型相对应

  • 实现:
    type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

    • ConstructorParameters<Type>相比,只需要对infer的使用换了个位置。
    class Person {
      constructor(name?: string, age?: number) { }
    }
    
    const T: InstanceType<typeof Person> = new Person()
    
    type T1 = InstanceType<typeof Person>
    type T2 = InstanceType<any>
    type T3 = InstanceType<never>
    type T4 = InstanceType<string>  // Error: string 不满足约束 abstract new (...args: any) => any
    type T5 = InstanceType<Function>  // Error: Function 不满足约束 abstract new (...args: any) => any
    

ThisParameterType<Type>(函数参数this的类型)

  • 获取函数参数的 this的类型,常用在call,apply,bind中。如果函数内部的第一个参数命名不是this,则会返回unknown

    function getThis(this: string, a: number) {
      console.log(a, this);
    }
    type getThisType = ThisParameterType<typeof getThis>;   // string
    
    /****2******/
    function getThis(a: number) { console.log(a); }
    type getThisType = ThisParameterType<typeof getThis>;   // unknown
    
    /****3******/
    function fncTest(this: Number) {
      return this.toString(16);
    }
    function fncTestThis(n: ThisParameterType<typeof fncTest>) {   // (parameter) n: Number
      return fncTest.apply(n);
    }
    
  • 实现:type ThisParameterType<T> = T extends (this: infer U, ...args: never) => any ? U : unknown;

OmitThisParameter<Type>

  • 获取不带this参数的新函数类型。同ThisParameterType<Type>this必须是函数的第一个参数。

    function getThis(this: string, a: number) {
      console.log(a, this);
    }
    type getThisType = OmitThisParameter<typeof getThis>;   // type getThisType = (a: number) => void
    
  • 实现:type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;

ThisType<Type>(上下文this类型的标记)

此实用程序不返回转换后的类型。相反,它充当上下文类型的标记。(Tips:必须启用noImplicitThis标志才能使用此实用程序)

  • 官网示例与解释:

    // Compile with --noImplicitThis
    type ObjectDescriptor<D, M> = {
      data?: D;
      methods?: M & ThisType<D & M>; // 方法中的“this”类型是 D & M
    };
    
    function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
      let data: object = desc.data || {};
      let methods: object = desc.methods || {};
      return { ...data, ...methods } as D & M;
    }
    
    let obj = makeObject({
      data: { x: 0, y: 0 },
      methods: {
        moveBy(dx: number, dy: number) {
          this.x += dx; // 强类型this
          this.y += dy; // 强类型this
        },
      },
    });
    
    obj.x = 10;
    obj.y = 20;
    obj.moveBy(5, 5);
    
    • 上示例中,makeObject参数中的对象属性methods有一个上下文类型:ThisType<D & M>,因此对象中methods属性下的方法中的this类型是{x: number, y: number} & {moveBy(dx: number, dy: number): void}

    • ThisType<T> 的接口,在 lib.d.ts 只是被声明为空的接口,除了可以在对象字面量上下文中可以被识别以外,该接口的作用等同于任意空接口。

  • (简单理解)

    • ThisType主要用于显式指定对象方法中this的类型,在JSthis引用的是当前方法所在的对象,但在TS中,有时需要更准确的指定this的类型;例如

      • 当对象有多个方法时,需要在每个方法中指定this的类型,以确保这些方法在使用this时不会发生类型错误;

      • 当对象方法作为参数传递给其它函数时,需要在类型中指定this类型,以确保在使用函数时this引用正确;

      interface Person {
        name: string;
        age: number;
        sayHi(): string;
      }
      
      // 使用 ThisType 来扩展 Person 接口并显式指定 this 的类型
      type PersonWithGreeting = Person & ThisType<Person>;
      
      // greet中 使用 this: PersonWithGreeting 参数来指定 this 的类型
      function greet(this: PersonWithGreeting, greeting: string) {
        return `${greeting}, ${this.name}!`;
      }
      
      const person: PersonWithGreeting = {
        name: "Tom",
        age: 20,
        sayHi() {
          return `Hi, ${this.name}!`;
        },
      };
      
      // 最后,通过 call() 方法调用 greet 方法,并指定 person 作为 this 对象,
      // 这样,在 greet 方法中,this 就表示 person 对象
      const greeting = greet.call(person, "Hello");
      
      console.log(greeting);        // Hello, Tom!
      console.log(person.sayHi());  // Hi, Tom!
      

内部字符串操作类型

  • Uppercase<StringType>StringType转为大写

  • Lowercase<StringType>StringType转为小写

  • Capitalize<StringType>StringType首字母大写

  • Uncapitalize<StringType>StringType首字母小写

type Eg1 = Uppercase<'abcd'>;     // type Eg1 = "ABCD"

type Eg2 = Lowercase<'ABCD'>;     // type Eg2 = "abcd"

type Eg3 = Capitalize<'Abcd'>;    // type Eg3 = "Abcd"

type Eg4 = Uncapitalize<'aBCD'>;  // type Eg4 = "aBCD"

扩展:自定义工具类型

1.获取不同时存在于 TU 内的类型

实现:type Exclusive<T, U> = Exclude<T | U, T & U>;

// 实现
type Exclusive<T, U> = Exclude<T | U, T & U>;

// 示例
type Eg = Exclusive<"a" | "b" | "c", "Er" | "a" | "b">   // type Eg = "c" | "Er"
  • 主要是利用Exclude获取存在与第一个参数但不存在与第二个参数的类型
  • 参数二T & U 获取的是所有类型的交叉类型;
  • 参数一T | U 这是利用在联合类型在extends中的分发特性,可以理解为Exclude<T, T & U> | Exclude<U, T & U>;

2.获取T中所有类型为函数的key组成的联合类型

type FunctionKeys<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

interface Test {
  foo(): void;
  bar: string;
  baz: () => number;
}

type TestFunction = FunctionKeys<Test>; // "foo" | "baz"
  • 实现:

    • K in keyof T: 先使用 keyof T 获取 T 中所有 key 组成的联合类型, 然后使用 映射类型 将这个联合类型转换为一个新的对象

    • T[] 是索引访问操作,可以取到值的类型;

    • T[K]为有效类型,则判断是否为Function类型,是的话返回K,否则never

    • 最后执行{...}[keyof T]索引访问获取最终结果

      • Tips1:T[keyof T] 则是获取T所有值的类型;

      • Tips2:never 和其他类型进行联合时,never 是不存在的,例如:never | number | string 等同于 number | string

3.获取对象中指定类型的字段(由2改)

根据指定类型U,在对象T中挑选出所有类型一致的字段并组成一个新的类型。

type TypeKeys<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

type PickByType<T, U> = Pick<T, TypeKeys<T, U>>;

interface Example {
  foo: string;
  bar: number;
  baz: string;
}

type StringFieldsOfExample = PickByType<Example, string>; // { foo: string, baz: string }
type NumberFieldsOfExample = PickByType<Example, number>; // { bar: number; }

4.从数组中提取指定属性的值(由2/3改)

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

function pluck<T, K extends keyof T>(arr: T[], key: K): PickKeys<T, K>[] {
  return arr.map((item) => item[key]);
}

interface Example3 {
  foo: string;
  bar: number;
}
const exampleList: Example3[] = [
  { foo: 'hello', bar: 1 },
  { foo: 'world', bar: 2 },
];

const fooList = pluck(exampleList, 'foo'); 
console.log('fooList', fooList);    // ["hello", "world"]

通过PickKeys从对象中提取指定属性的值。定义pluck函数,用于从数组中提取指定属性的值,并返回一个新的数组

5.查找T所有非只读类型的key组成的联合类型

/**
 * 核心实现
 */
type MutableKeys<T extends object> = {
  [P in keyof T]-?: IfEquals<
    { [Q in P]: T[P] },
    { -readonly [Q in P]: T[P] },
    P
  >;
}[keyof T];

/**
 * @desc 一个辅助类型,判断X和Y是否类型相同,
 * @returns 是则返回A,否则返回B
 */
type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
  ? A
  : B;

示例:MutableKeys只适用一维对象,无法对嵌套对象生效

/**
 * 示例
 */
interface Person {
  readonly name: string;
  age: number;
  address: {
    readonly street: string;
    city: string;
  };
}


type T0 = MutableKeys<Person>   // type T0 = "age" | "address"

// 测试
type Test = Pick<Person, MutableKeys<Person>>
/**
 * =>
 * type Test = {
 *  age: number;    
 *  address: {       
 *    readonly street: string;
 *    city: string;
 *  };
 * }
 */
const person: Person = {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "Springfield",
  },
};
const P2: Test = {
  age: 32,
  address: {
    street: "123 Main St",
    city: "Springfield",
  },
};

6.获取T中所有的可选项或key

type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

使用映射遍历所有Key,通过Pick<T, K>提取当前Key和类型;

利用{} extends {当前key: 类型}判断是否为可选类型。

// Eg2 = false
type Eg2 = {} extends {key1: string} ? true : false;
// Eg3 = true
type Eg3 = {} extends {key1?: string} ? true : false;

利用的就是{}和只包含可选参数类型{key?: string}是兼容的这一特性。把extends前面的{}替换成object也是可以的。

/**
 * 测试例
 */
interface E1 {
  a: string;
  b?: number;
  c?: boolean;
  c2: string;
}
/**
 * T 中所有可选项的key的联合类型
 * type K1 = "b" | "c"
 */
type K1 = OptionalKeys<E1>;

/**
 * T 中所有可选项
 * type T1 = {
 *    b?: number | undefined;
 *    c?: boolean | undefined;
 * }
 */
type T1 = Pick<E1, K1>

7.Pick扩展:获取T中指定类型的所有项

/**
 * 辅助函数
 * 用于获取 T 中类型不为 never 的类型组成的联合类型
 */
type TypeKeys<T> = T[keyof T];

/**
 * 核心实现
 * undefined 的存在 可以获取可选项的值
 */
type PickByValue<T, V> = Pick<T,
  TypeKeys<{ [P in keyof T]: T[P] extends V | undefined ? P : never }>
>;


// 示例
interface E1 {
  a: string;
  a2?: string;
  b: number;
  b1: number | string;
  b2?: number;
}

type T1 = PickByValue<E1, string>
/**
 * type T1 = {
 *   a: string;
 *   a2?: string | undefined;
 * }
 * ----------------------------------------
 * 注意: 如果将 T[P] extends V | undefined 中的 undefined 去掉,则拿到的值为
 *  type T1 = {
 *    a: string;
 *  }
 */

如果 T 中属性的值可能是多个类型并且这些类型之间存在兼容性关系(比如 number | string),需要使用 Extract 类型来进行类型匹配。

type TypeKeys<T> = T[keyof T];

type PickByValueExact<T, V> = Pick<T,
  TypeKeys<{[P in keyof T]: Extract<T[P], V> extends never ? never : P}>
>;


interface E1 {
  a: string;
  a1: string | boolean;
  b: number;
  b1: number | string;
  c?: boolean;
}
type T1 = PickByValueExact<E1, boolean>
/**
 *  type T1 = {
 *    a1: string | boolean;
 *    c?: boolean | undefined;
 *  }
 */

8.删除T中指定类型的所有项(由7改)

type TypeKeys<T> = T[keyof T];

type OmitByValueExact<T, V> = Omit<T,
  TypeKeys<{[P in keyof T]: Extract<T[P], V> extends never ? never : P}>
>;
/**
 * 等同于(Pick写法) =>
 * type OmitByValueExact<T, V> = Pick<T,
 *  TypeKeys<{ [P in keyof T]: Extract<T[P], V> extends never ? P : never }>
 * >;
 */

// 示例
interface E1 {
  a: string;
  a1: string | boolean;
  b: number;
  b1: number | string;
  c?: boolean;
}
type T1 = OmitByValueExact<E1, boolean>
/**
 *  type T1 = {
 *    a: string;
 *    b: number;
 *    b1: number | string;
 *  }
 */

9.Pick扩展:获取 TU共同存在key和对应类型

// T extends object, U extends object
type Intersection<T, U> = Pick<T, Extract<keyof U, keyof T> & Extract<keyof T, keyof U>>

// 使用
interface E1 {
  a: string;
  a1: string | boolean;
  b: number;
  b2?: number;
}

interface E2 {
  a: string;
  a1: boolean;
  b2?: number;
}
type T1 = Intersection<E1, E2>
// type T1 = {
//   a: string;
//   a1: string | boolean;
//   b2?: number | undefined;
// }

2次Extract的原因是为了避免类型的兼容推导问题。

10.去除T中存在于Ukey和对应类型(由9改)

type Diff<T, U> = Pick<T, Exclude<keyof T, keyof U>>

// 使用
interface E1 {
  a: string;
  a1: string | boolean;
  b: number;
  b2?: number;
}

interface E2 {
  a: string;
  a1: boolean;
  b2?: number;
  c: string
}

type T1 = Diff<E1, E2>
// type T1 = {
//   b: number;
// }

11.OverwriteAssign(9/10改)

  • Assign
// 合并,相同key和对应类型由后者覆盖前者
type Assign<T, U, I = Diff<T, U> & U> = Pick<I, keyof I>;

// 示例
interface E1 {
  a: string;
  b: string | boolean;
}

interface E2 {
  a: string;
  c: string
}

type Eg2 = Assign<E1, E2>
// type Eg2 = {
//   a: string;
//   b: string | boolean;
//   c: string;
// }
  • Overwrite
// 获取前者独有的key和类型,再取两者共有的key和该key在后者中的类型,最后合并。
// T extends object, U extends object
type Overwrite<T, U, I = Diff<T, U> & Intersection<U, T>> = Pick<I, keyof I>

interface E1 {
  a: string;
  b3: number;
}

interface E2 {
  a: string;
  a1: boolean;
  b2: number;
  c: string
}
type Eg1 = Overwrite<E1, E2>
// type Eg1 = {
//   a: string;
//   b3: number;
// }

12.将联合类型转变成交叉类型

type UnionToIntersection<T> = (
  T extends any ? (arg: T) => void : never
) extends (arg: infer U) => void ? U : never;


interface A {
  a: string;
}

interface B {
  b: number;
}

type C = UnionToIntersection<A | B>; 
// { a: string } & { b: number }
  • T extends any ? (arg: T) => void : never: 如果类型 T 可以被转化为任意类型,则返回一个接受参数为 T 类型的函数;否则返回 never 类型。

  • (arg: infer U) => void: infer关键字来推断函数类型所接收的参数类型并定义一个新的类型U

  • 利用第二个extends配合infer推导得到U的类型,利用infer对协变类型的特性得到交叉类型。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容