前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TypeScript 类型体操 - 进阶

TypeScript 类型体操 - 进阶

作者头像
Cellinlab
发布2023-05-17 17:04:45
3310
发布2023-05-17 17:04:45
举报
文章被收录于专栏:Cellinlab's BlogCellinlab's Blog

# 内置高级类型

# 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 | ?developer/article/2287851/undefined;
//   age?: number | ?developer/article/2287851/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 | ?developer/article/2287851/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 或者 ?developer/article/2287851/undefined 的类型的:

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

type NonNullableResult = NonNullable<string | null | ?developer/article/2287851/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 | ?developer/article/2287851/undefined;
//     age?: number | ?developer/article/2287851/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 | ?developer/article/2287851/undefined;
//     c?: 3 | ?developer/article/2287851/undefined;
// }
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022/3/19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # 内置高级类型
    • # Parameters
      • # ReturnType
        • # ConstructorParameters
          • # InstanceType
            • # ThisParameterType
              • # OmitThisParameter
                • # Partial
                  • # Required
                    • # Readonly
                      • # Pick
                        • # Record
                          • # Exclude
                            • # Extract
                              • # Omit
                                • # Awaited
                                  • # NonNullable
                                    • # Uppercase, Lowercase, Capitalize, Uncapitalize
                                    • # 类型编程实战
                                      • # ParseQueryString
                                        • # KebabCaseToCamelCase
                                          • # CamelCaseToKebabCase
                                            • # Chunk
                                              • # TupleToNestedObject
                                                • # PartialObjectPropByKeys
                                                  • # 函数重载
                                                    • # UnionToTuple
                                                      • # join
                                                        • # DeepCamelize
                                                          • # AllKeyPath
                                                            • # Defaultize
                                                            领券
                                                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                                                            http://www.vxiaotou.com