TypeScript 类型体操 - 进阶

2023-05-17 17:04:45 浏览数 (1)

# 内置高级类型

# Parameters

代码语言:javascript复制
type Parameters<T extends (...args: any) => any)
  = T extends (...args: infer P) => any
    ? P
    : never;

type ParametersResult = Parameters<(name: string, age: number) => void>;
// type ParametersResult = [name: string, age: number]

# ReturnType

代码语言:javascript复制
type ReturnType<T extends (...args: any) => any)
  = T extends (...args: any) => infer R
    ? R
    : never;

type ReturnTypeResult = ReturnType<(name: string, age: number) => string>;
// type ReturnTypeResult = string

# ConstructorParameters

代码语言:javascript复制
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (
  ...args: infer P
) => any
  ? P
  : never;

type ConstructorParametersResult = ConstructorParameters<new (name: string, age: number) => void>;
// type ConstructorParametersResult = [name: string, age: number]

# InstanceType

代码语言:javascript复制
interface Person {
  name: string;
  age: number;
}

interface PersonConstructor {
  new (name: string, age: number): Person;
}

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

type InstanceTypeResult = InstanceType<PersonConstructor>;
// type InstanceTypeResult = Person

# ThisParameterType

代码语言:javascript复制
type Person = {
  name: "cell";
};
function say(this: Person) {
  console.log(this.name);
}

type ThisParameterType<T> = T extends (this: infer P, ...args: any[]) => any ? P : unknown;

type ThisParameterTypeResult = ThisParameterType<typeof say>;
// type ThisParameterTypeResult = {
//   name: "cell";
// }

# OmitThisParameter

代码语言:javascript复制
type Person = {
  name: "cell";
};
function say(this: Person, age: number) {
  console.log(this.name);
  return this.name   "-"   age;
}

type OmitThisParameter<T> = unknown extends ThisParameterType<T>
  ? T
  : T extends (...args: infer A) => infer R
  ? (...args: A) => R
  : T;

type sayType = typeof say;
// type sayType = (this: Person, age: number) => string
type OmitThisParameterResult = OmitThisParameter<sayType>;
//type OmitThisParameterResult = (age: number) => string

# Partial

代码语言:javascript复制
type Partial<T> = {
  [P in keyof T]?: T[P];
};

type PartialResult = Partial<{ name: string; age: number }>;
// type PartialResult = {
//   name?: string | undefined;
//   age?: number | undefined;
// }

# Required

代码语言:javascript复制
type Required<T> = {
  [P in keyof T]-?: T[P];
};

type RequiredResult = Required<{ name?: string; age?: number }>;
// type RequiredResult = {
//   name: string;
//   age: number;
// }

# Readonly

代码语言:javascript复制
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type ReadonlyResult = Readonly<{ name: string; age: number }>;
// type ReadonlyResult = {
//   readonly name: string;
//   readonly age: number;
// }

# Pick

映射类型的语法用于构造新的索引类型,在构造的过程中可以对索引和值做一些修改或过滤。

比如可以用 Pick 实现过滤:

代码语言:javascript复制
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type PickResult = Pick<{ name: string; age: number }, "name">;
// type PickResult = {
//   name: string;
// }

# Record

Record 用于创建索引类型,传入 key 的类型:

代码语言:javascript复制
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type RecordResult = Record<"name" | "age", string>;
// type RecordResult = {
//   name: string;
//   age: string;
// }

注意,此处 keyof any 结果是 string | number | symbol,所以 Recordkey 可以是 stringnumbersymbol

# Exclude

从一个联合类型中去掉一部分类型时,可以用 Exclude 类型:

代码语言:javascript复制
type Exclude<T, U> = T extends U ? never : T;

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

# Extract

Exclude 反过来就是 Extract,也就是取交集:

代码语言:javascript复制
type Extract<T, U> = T extends U ? T : never;

type ExtractResult = Extract<"a" | "b" | "c", "a" | "b">;
// type ExtractResult = "a" | "b"

# Omit

Omit 用于从一个类型中去掉某些属性:

代码语言:javascript复制
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type OmitResult = Omit<{ name: string; age: number }, "name">;
// type OmitResult = {
//   age: number;
// }

# Awaited

PromiseValuType 的高级类型:

代码语言:javascript复制
type Awaited<T> = T extends null | undefined
  ? T
  : T extends object & { then(onfulfilled: infer F): any }
  ? F extends (value: infer V, ...args: any) => any
    ? Awaited<V>
    : never
  : T;

type AwaitedResult = Awaited<Promise<string>>;
// type AwaitedResult = string

# NonNullable

NonNullable 用于判断是否为非空类型,也就是不是 null 或者 undefined 的类型的:

代码语言:javascript复制
type NonNullable<T> = T extends null | undefined ? never : T;

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

# Uppercase, Lowercase, Capitalize, Uncapitalize

代码语言:javascript复制
type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;

intrinsic 是固有的意思,就像 js 里面的有的方法打印会显示 [native code] 一样。这部分类型不是在 ts 里实现的,而是编译过程中由 js 实现的。

# 类型编程实战

类型编程可以动态生成类型,对已有类型做修改。

类型编程的意义:需要动态生成类型的场景,必然要用类型编程做一些运算。有的场景下可以不用类型编程,但是用了能够有更精准的类型提示和检查。

# ParseQueryString

JavaScript 实现:

代码语言:javascript复制
function parseQueryString(queryStr) {
  if (!queryStr || !queryStr.length) return {};
  const queryObj = {};
  const queryArr = queryStr.split("&");
  for (let i = 0; i < queryArr.length; i  ) {
    const [key, value] = queryArr[i].split("=");
    if (queryObj[key]) {
      if (Array.isArray(queryObj[key])) {
        queryObj[key].push(value);
      } else {
        queryObj[key] = [queryObj[key], value];
      }
    } else {
      queryObj[key] = value;
    }
  }
  return queryObj;
}

const result = parseQueryString("a=1&a=2&b=3");
// {
//     "a": [
//         "1",
//         "2"
//     ],
//     "b": "3"
// }

类型推导:

代码语言:javascript复制
type ParseParam<Param extends string> = Param extends `${infer K}=${infer V}`
  ? { [key in K]: V }
  : Record<string, any>;

type MergeValues<One, Other> = One extends Other
  ? One
  : Other extends unknown[]
  ? [One, ...Other]
  : [One, Other];

type MergeParams<OneParam extends Record<string, any>, OtherParam extends Record<string, any>> = {
  readonly [K in keyof OneParam | keyof OtherParam]: K extends keyof OneParam
    ? K extends keyof OtherParam
      ? MergeValues<OneParam[K], OtherParam[K]>
      : OneParam[K]
    : K extends keyof OtherParam
    ? OtherParam[K]
    : never;
};

type ParseQueryString<Str extends string> = Str extends `${infer First}&${infer Rest}`
  ? MergeParams<ParseParam<First>, ParseQueryString<Rest>>
  : ParseParam<Str>;

function parseQueryString<Str extends string>(queryStr: Str): ParseQueryString<Str>;

function parseQueryString(queryStr: string) {
  if (!queryStr || !queryStr.length) return {};
  const queryObj: Record<string, any> = {};
  const queryArr = queryStr.split("&");
  for (let i = 0; i < queryArr.length; i  ) {
    const [key, value] = queryArr[i].split("=");
    if (queryObj[key]) {
      if (Array.isArray(queryObj[key])) {
        queryObj[key].push(value);
      } else {
        queryObj[key] = [queryObj[key], value];
      }
    } else {
      queryObj[key] = value;
    }
  }
  return queryObj;
}

const result = parseQueryString("a=1&a=2&b=3");

# KebabCaseToCamelCase

代码语言:javascript复制
type KebabCaseToCamelCase<Str extends string> = Str extends `${infer Cursor}-${infer Rest}`
  ? `${Cursor}${KebabCaseToCamelCase<Capitalize<Rest>>}`
  : Str;

type Result = KebabCaseToCamelCase<"hello-world">;
// type Result = "helloWorld"

# CamelCaseToKebabCase

代码语言:javascript复制
type CamelCaseToKebabCase<Str extends string> = Str extends `${infer Cursor}${infer Rest}`
  ? Cursor extends Lowercase<Cursor>
    ? `${Cursor}${CamelCaseToKebabCase<Rest>}`
    : `-${Lowercase<Cursor>}${CamelCaseToKebabCase<Rest>}`
  : Str;

type Result = CamelCaseToKebabCase<"helloWorld">;
// type Result = "hello-world"

# Chunk

TypeScript 实现:

代码语言:javascript复制
function Chunk(
  Arr: Array<number>,
  ItemLen: number,
  CurItem: Array<number>,
  Result: Array<Array<number>>
): any {
  const [cursor, ...Rest] = Arr;
  if (cursor) {
    if (CurItem.length === ItemLen) {
      return Chunk(Rest, ItemLen, [cursor], [...Result, CurItem]);
    } else {
      return Chunk(Rest, ItemLen, [...CurItem, cursor], Result);
    }
  } else {
    return [...Result, CurItem];
  }
}

const res = Chunk([1, 2, 3, 4, 5, 6, 7, 8], 3, [], []);
// const res = [
//     [1, 2, 3],
//     [4, 5, 6],
//     [7, 8]
// ]

类型编程:

代码语言:javascript复制
type Chunk<
  Arr extends unknown[],
  ItemLen extends number,
  CurItem extends unknown[] = [],
  Result extends unknown[][] = []
> = Arr extends [infer Cursor, ...infer Rest]
  ? CurItem["length"] extends ItemLen
    ? Chunk<Rest, ItemLen, [Cursor], [...Result, CurItem]>
    : Chunk<Rest, ItemLen, [...CurItem, Cursor], Result>
  : [...Result, CurItem];

type Result = Chunk<[1, 2, 3, 4, 5, 6, 7, 8], 3>;
// type Result = [
//     [1, 2, 3],
//     [4, 5, 6],
//     [7, 8]
// ]

# TupleToNestedObject

代码语言:javascript复制
type TupleToNestedObject<Tuple extends unknown[], Value> = Tuple extends [
  infer Cursor,
  ...infer Rest
]
  ? {
      [Key in Cursor as Key extends keyof any ? Key : never]: Rest extends unknown[]
        ? TupleToNestedObject<Rest, Value>
        : Value;
    }
  : Value;

type Result = TupleToNestedObject<["a", "b", "c"], 1>;
// type Result = {
//     a: {
//         b: {
//             c: 1
//         }
//     }
// }

# PartialObjectPropByKeys

代码语言:javascript复制
interface Person {
  name: string;
  age: number;
  phone: string;
}

type Copy<Obj extends Record<string, any>> = {
  [K in keyof Obj]: Obj[K];
};

type PartialObjectPropByKeys<T extends Record<string, any>, Keys extends keyof T> = Copy<
  Partial<Pick<T, Extract<keyof T, Keys>>> & Omit<T, Keys>
>;

type Result = PartialObjectPropByKeys<Person, "name" | "age">;
// type Result = {
//     name?: string | undefined;
//     age?: number | undefined;
//     phone: string;
// }

# 函数重载

ts 支持函数重载,也就是同名的函数可以有多种类型定义:

重载的写法一共有三种:

代码语言:javascript复制
// 同名函数重载
declare function func(age: number): number;
declare function func(age: string): string;

// 有函数实现可以不用带 declare
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x   y;
}

// 用 interface 声明函数 和 函数重载
interface Func1 {
  (age: number): number;
  (age: string): string;
}

declare const func1: Func1;

// 使用交叉类型实现函数重载
type Func2 = ((name: string) => string) & ((name: number) => number);

declare const func2: Func2;

# UnionToTuple

取重载函数的 ReturnType 返回的是最后一个重载的返回值类型:

代码语言:javascript复制
declare function func(age: number): number;
declare function func(age: string): string;

type Result = ReturnType<typeof func>;
// type Result = string

interface Func1 {
  (age: number): number;
  (age: string): string;
}

type Result1 = ReturnType<Func1>;
// type Result1 = string

type Func2 = ((name: string) => string) & ((name: number) => number);

type Result2 = ReturnType<Func2>;
// type Result2 = number

重载函数能通过函数交叉的方式写,并且也能实现联合转交叉,所以就能拿到联合类型的最后一个类型:

代码语言:javascript复制
type UnionToIntersection<U> = (U extends U ? (x: U) => unknown : never) extends (
  x: infer I
) => unknown
  ? I
  : never;

type UnionToFuncIntersection<T> = UnionToIntersection<T extends any ? () => T : never>;

type UnionToTuple<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R
  ? [...UnionToTuple<Exclude<T, R>>, R]
  : [];

type Result = UnionToTuple<1 | 2 | 3>;
// type Result = [1, 2, 3]

# join

代码语言:javascript复制
declare function join<Delimiter extends string>(
  delimiter: Delimiter
): <Items extends string[]>(...parts: Items) => JoinType<Items, Delimiter>;

type RemoveFirstDelimiter<Str extends string> = Str extends `${infer _}${infer Rest}` ? Rest : Str;

type JoinType<
  Items extends any[],
  Delimiter extends string,
  Result extends string = ""
> = Items extends [infer Cur, ...infer Rest]
  ? JoinType<Rest, Delimiter, `${Result}${Delimiter}${Cur & string}`>
  : RemoveFirstDelimiter<Result>;

let res = join("-")("cell", "in", "lab");
// let res: "cell-in-lab"

# DeepCamelize

代码语言:javascript复制
type CamelizeArr<Arr> = Arr extends [infer First, ...infer Rest]
  ? [DeepCamelize<First>, ...CamelizeArr<Rest>]
  : [];

type DeepCamelize<Obj extends Record<string, any>> = Obj extends unknown[]
  ? CamelizeArr<Obj>
  : {
      [Key in keyof Obj as Key extends `${infer First}_${infer Rest}`
        ? `${First}${Capitalize<Rest>}`
        : Key]: DeepCamelize<Obj[Key]>;
    };

type Result = DeepCamelize<{
  a_b: {
    c_d: {
      e_f: 1;
    };
  };
  g_h: [1, 2, 3];
}>;
// type Result = {
//     aB: {
//         cD: {
//             eF: 1;
//         };
//     };
//     gH: [1, 2, 3];
// }

# AllKeyPath

代码语言:javascript复制
type AllKeyPath<Obj extends Record<string, any>> = {
  [Key in keyof Obj]: Key extends string
    ? Obj[Key] extends Record<string, any>
      ? Key | `${Key}.${AllKeyPath<Obj[Key]>}`
      : Key
    : never;
}[keyof Obj];

type Result = AllKeyPath<{
  a: {
    b: {
      c: 1;
    };
  };
  d: 2;
}>;
// type Result = "a" | "a.b" | "a.b.c" | "d"

# Defaultize

代码语言:javascript复制
type Defaultize<A, B> = Pick<A, Exclude<keyof A, keyof B>> &
  Partial<Pick<A, Extract<keyof A, keyof B>>> &
  Partial<Pick<B, Exclude<keyof B, keyof A>>>;

type Copy<Obj extends Record<string, any>> = {
  [Key in keyof Obj]: Obj[Key];
};

type PreResult = Defaultize<{ a: 1; b: 2 }, { a: 1; c: 3 }>;
type Result = Copy<PreResult>;
// type Result = {
//     a: 1;
//     b?: 2 | undefined;
//     c?: 3 | undefined;
// }

0 人点赞