Desmond

Desmond

An introvert who loves web programming, graphic design and guitar
github
bilibili
twitter

TypeScript ツールタイプ

属性修饰ツールタイプ#

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

type DeepPartial<T extends object> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

type Required<T> = {
    [P in keyof T]-?: T[P];
};

type DeepRequired<T extends object> = {
  [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
};

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

type DeepReadonly<T extends object> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

type Mutable<T> = {
    -readonly [P in keyof T]: T[P];
};

type DeepMutable<T extends object> = {
  -readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K];
};
type NonNullable<T> = T extends null | undefined ? never : T;

export type DeepNonNullable<T extends object> = {
  [K in keyof T]: T[K] extends object
    ? DeepNonNullable<T[K]>
    : NonNullable<T[K]>;
};

type Nullable<T> = T | null;

type DeepNullable<T extends object> = {
  [K in keyof T]: T[K] extends object ? DeepNullable<T[K]> : Nullable<T[K]>;
};

複雑なツールタイプを基本ツールタイプとタイプツールの組み合わせに分解する (分割 - 処理 - 組み合わせ)

type Flatten<T> = { [K in keyof T]: T[K] };

type MarkPropsAsOptional<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Partial<Pick<T, K>> & Omit<T, K>>;

type MarkPropsAsRequired<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Required<Pick<T, K>>>;

type MarkPropsAsReadonly<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Readonly<Pick<T, K>>>;

type MarkPropsAsMutable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Mutable<Pick<T, K>>>;

type MarkPropsAsNullable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Nullable<Pick<T, K>>>;

type MarkPropsAsNonNullable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & NonNullable<Pick<T, K>>>;

構造ツールタイプ#

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

type Dictionary<T> = {
  [index: string]: T;
};

type NumericDictionary<T> = {
  [index: number]: T;
};

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

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Exclude<A, B> の結果は、ユニオンタイプ A の中で B に存在しない部分です。

キー値タイプに基づく Pick と Omit#

type ExpectedPropKeys<T extends object, ValueType> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? Key : never;
}[keyof T];

type PickByValueType<T extends object, ValueType> = Pick<
  T,
  ExpectedPropKeys<T, ValueType>
>;
type FilteredPropKeys<T extends object, ValueType> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? never : Key;
}[keyof T];

type OmitByValueType<T extends object, ValueType> = Pick<
  T,
  FilteredPropKeys<T, ValueType>
>;
type StrictConditional<A, B, Resolved, Rejected, Fallback = never> = [
  A
] extends [B]
  ? [B] extends [A]
    ? Resolved
    : Rejected
  : Fallback;

type StrictValueTypeFilter<
  T extends object,
  ValueType,
  Positive extends boolean = true
> = {
  [Key in keyof T]-?: StrictConditional<
    ValueType,
    T[Key],
    // ネストが多くなるのを避けるため、ここでは Conditional を使用しません
    Positive extends true ? Key : never,
    Positive extends true ? never : Key,
    Positive extends true ? never : Key
  >;
}[keyof T];

type StrictPickByValueType<T extends object, ValueType> = Pick<
  T,
  StrictValueTypeFilter<T, ValueType>
>;

type StrictOmitByValueType<T extends object, ValueType> = Pick<
  T,
  StrictValueTypeFilter<T, ValueType, false>
>;

サブ構造の排他的処理#

ユーザー情報を記述するオブジェクト構造を想像してください。共通の基本構造に加えて、VIP ユーザー、一般ユーザー、訪問者の 3 種類のユーザーがそれぞれ独自のフィールドを持っています。例えば、vipExpires は VIP ユーザー専用のフィールドであり、promotionUsed は一般ユーザーに属し、refererType は訪問者に属します。

interface VIP {
  vipExpires: number;
}

interface CommonUser {
  promotionUsed: boolean;
}

interface Visitor {
  refererType: RefererType;
}

type User = VIP | CommonUser;

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);

type XORUser = XOR<VIP, XOR<CommonUser, Visitor>>;

排他的なタイプを使用してバインディング効果を実現することもできます。つまり、A、B 属性のいずれかを同時に持つか、またはどちらの属性も持たないということです:

type XORStruct = XOR<
  {},
  {
    foo: string;
    bar: number;
  }
>;

集合ツールタイプ#

type Extract<T, U> = T extends U ? T : never;

type Exclude<T, U> = T extends U ? never : T;

// 和集合
type Concurrence<A, B> = A | B;

// 積集合
type Intersection<A, B> = A extends B ? A : never;

// 差集合
type Difference<A, B> = A extends B ? never : A;

// 補集合
type Complement<A, B extends A> = Difference<A, B>;

type NonNullable<T> = T extends null | undefined ? never : T;

type _NonNullable<T> = Difference<T, null | undefined>

ここでの具体的な実装は、条件タイプの分散特性に基づいています。つまり、T と U が両方ともユニオンタイプ(集合として扱われる)である場合、T のメンバーは順次 extends U ? T1 : T2 の計算を行い、最終的な結果を再びユニオンタイプとして結合します。

オブジェクトのプロパティ名のバージョンを実装します:

// より正確なオブジェクトタイプで構造を記述
type PlainObjectType = Record<string, any>;

// プロパティ名の和集合
type ObjectKeysConcurrence<
  T extends PlainObjectType,
  U extends PlainObjectType
> = keyof T | keyof U;

// プロパティ名の積集合
type ObjectKeysIntersection<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Intersection<keyof T, keyof U>;

// プロパティ名の差集合
type ObjectKeysDifference<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Difference<keyof T, keyof U>;

// プロパティ名の補集合
type ObjectKeysComplement<
  T extends U,
  U extends PlainObjectType
> = Complement<keyof T, keyof U>;

積集合、補集合、差集合については、プロパティ名の集合を直接使用してオブジェクトレベルのバージョンを実装できます:

type ObjectIntersection<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Pick<T, ObjectKeysIntersection<T, U>>;

type ObjectDifference<
  T extends PlainObjectType,
  U extends PlainObjectType
> = Pick<T, ObjectKeysDifference<T, U>>;

type ObjectComplement<T extends U, U extends PlainObjectType> = Pick<
  T,
  ObjectKeysComplement<T, U>
>;
type Merge<
  T extends PlainObjectType,
  U extends PlainObjectType
  // T が U より多い部分に加え、T と U の交集部分(タイプが異なる場合は U の優先度が高く、さらに U が T より多い部分を加えます)
> = ObjectDifference<T, U> & ObjectIntersection<U, T> & ObjectDifference<U, T>;
type Assign<
  T extends PlainObjectType,
  U extends PlainObjectType
  // T が U より多い部分に加え、T と U の交集部分(タイプが異なる場合は T の優先度が高く、さらに U が T より多い部分を加えます)
> = ObjectDifference<T, U> & ObjectIntersection<T, U> & ObjectDifference<U, T>;
type Override<
  T extends PlainObjectType,
  U extends PlainObjectType
  // T が U より多い部分に加え、T と U の交集部分(タイプが異なる場合は U の優先度が高い(逆和集合))
> = ObjectDifference<T, U> & ObjectIntersection<U, T>;

パターンマッチングツールタイプ#

type FunctionType = (...args: any) => any;

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

type ReturnType<T extends FunctionType> = T extends (...args: any) => infer R ? R : any;

type FirstParameter<T extends FunctionType> = T extends (
  arg: infer P,
  ...args: any
) => any
  ? P
  : never;

type LastParameter<T extends FunctionType> = T extends (arg: infer P) => any
  ? P
  : T extends (...args: infer R) => any
  ? R extends [...any, infer Q]
    ? Q
    : never
  : never;
type ClassType = abstract new (...args: any) => any;
interface ClassType<TInstanceType = any> {
    new (...args: any[]): TInstanceType;
}

type ConstructorParameters<T extends ClassType> = T extends abstract new (
  ...args: infer P
) => any
  ? P
  : never;

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

TypeScript 4.7 は infer 制約機能をサポートし、特定のタイプの抽出を実現します。

type FirstArrayItemType<T extends any[]> = T extends [infer P extends string, ...any[]]
  ? P
  : never;

以前に説明した Promise 内部値タイプを抽出するツールタイプ PromiseValue について、TypeScript の組み込みツールタイプにも同様の機能を持つものがあり、その実装はより厳密です:

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;

References:
https://juejin.cn/book/7086408430491172901

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。