Skip to content

深入学习 TypeScript 类型体操 #31

Open
@Bulandent

Description

@Bulandent

模式匹配做提取

数组类型

提取数组第一个元素的类型

type GetFirst<Arr extends unknown[]> =
	Arr extends [infer First, ...unknown[]] ? First : never;

提取数组最后一个元素的类型

type GetLast<Arr extends unknown[]> =
	Arr extends [...unknown[], infer Last] ? Last : never;

取去掉最后一个元素的数组

type PopArr<Arr extends unknown[]> = 
	Arr extends [] ? []
		: Arr extends [...infer Rest, unknown] ? Rest : never;

字符串类型

字符串是否以某个特定字符开头

type StartWidth<Str extends string, Prefix extends String> = 
	Str extends `${Prefix}${string}` ? true : false;

将字符串中某个特定的部分替换成别的字符串

type ReplaceStr<
    Str extends string,
    From extends string,
    To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}` 
        ? `${Prefix}${To}${Suffix}` : Str;

字符串去除右空格

type TrimStrRight<Str extends string> =
    Str extends `${infer Rest}${' ' | '\n' | '\t'}`
        ? TrimStrRight<Rest> : Str;

函数类型

提取函数参数的类型

type GetParameters<Func extends Function> = 
	Func extends (...args: infer Args) => unknown ? Args : never;

提取函数返回值的类型

type GetReturnType<Func extends Function> = 
	Func extends (...args: any[]) => infer ReturnType
		? ReturnType : never;

构造器

提取构造器参数的类型

type GetInstanceParameters<
    ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (...args: infer ParametersType) => any 
        ? ParametersType : never;

提取构造器返回值类型

type GetInstanceReturnType<
    ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (...args: any) => infer InstanceType 
        ? InstanceType : never;

索引类型

提取 props 中 ref 值的类型

type GetRefProps<Props> =
    'ref' extends keyof Props
        ? Props extends { ref?: infer Value | undefined}
            ? Value : never
        : never;

重新构造做变换

TypeScript 的 type、infer、类型参数声明的变量都不能修改,想对类型做各种变换产生新的类型就需要重新构造。

数组类型的构造

给数组/元组添加新类型

type Push<Arr extends unknown[], Ele> = [...Arr, Ele];

元组重组

type tuple1 = [1,2];
type tuple2 = ['guang', 'dong'];
// 重组成如下的元组
type tuple = [[1, 'guang'], [2, 'dong']];

// 代码实现
type Zip<One extends unknown[], Other extends unknown[]> = 
    One extends [infer OneFirst, ...infer OneRest]
        ? Other extends [infer OtherFirst, ...infer OtherRest]
            ? [[OneFirst, OtherFirst], ...Zip<OneRest, OtherRest>] : []
                : [];

字符串类型的构造

将字符串首字母大写

type CapitalizeStr<Str extends string> = 
    Str extends `${infer First}${infer Rest}`
        ? `${Uppercase<First>}${Rest}` 
        : Str;

将字符串下划线转驼峰

```tstype CamelCase<Str extends string> = Str extends${infer Left}_${infer Right}${infer Rest}`
? `${Left}${Uppercase}${CamelCase}`
: Str;


**删除字符串子串**

```ts
type DropSubStr<Str extends string, SubStr extends string> =
    Str extends `${infer Prefix}${SubStr}${infer Suffix}`
        ? DropSubStr<`${Prefix}${Suffix}`, SubStr>
        : Str;

函数类型的构造

给函数添加一个参数

type AppendArgument<Func extends Function, Arg> = 
    Func extends (...args: infer Args) => infer ReturnType
        ? (...args: [...Args, Arg]) => ReturnType
        : never;

索引类型的构造

索引类型是聚合多个元素的类型。比如 class 和对象都是索引类型。索引类型的元素的类型只能是 string、number 或者 Symbol 等类型。

索引类型的每个元素的类型可以添加修饰符:readonly(只读)、?(可选)。

映射类型语法

type Mapping<Obj extends object> = {
    [Key in keyof Obj]: Obj[Key]
}

用 as 做重映射改变索引类型的 Key 转成大写

type UppercaseKey<Obj extends object> = {
    [Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}

因为这里索引的类型可能是 string、number 或 symbol 类型,但是这里转成大写只能是限定为 string。

TS内置高级类型 Record

type Record<K extends string | number | symbol, T> = {[P in K]: T}

UppercaseKey 重写版:用 Record 来约束索引类型而不是 object

type UppercaseKey<Obj extends Record<string, any>> = {
    [Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}

给索引类型添加只读的高级类型

type ToReadonly<T> = {
    readonly [Key in keyof T]: T[Key]
}

给索引类型添加可选的高级类型

type ToPartial<T> = {
    [Key in keyof T]?: T[Key]
}

给索引类型去掉只读修饰符

type ToMutable<T> = {
    -readonly [Key in keyof T]: T[Key]
}

给索引类型去掉可选修饰符

type ToRequired<T> = {
    [Key in keyof T]-?: T[Key]
}

返回特定值的类型的索引类型

type FilterByValueType<Obj extends Record<string, any>, ValueType> = {
    [Key in keyof Obj 
        as Obj[Key] extends ValueType ? Key : never]
        : Obj[Key]
}

递归复用做循环

Promise 的递归复用

提取 Promise 值的类型

type DeepPromiseValueType<P extends Promise<unknown>> = 
    P extends Promise<infer ValueType>
        ? ValueType extends Promise<unknown>
            ? DeepPromiseValueType<ValueType>
            : ValueType
        : never;

提取 Promise 值的类型简化版

type DeepPromiseValueType<T> = 
    T extends Promise<infer ValueType>
        ? DeepPromiseValueType<ValueType>
        : never;

数组类型的递归

反转元组

type ReversrArr<Arr extends unknown[]> = 
    Arr extends [infer First, ...infer Rest]
        ? [...ReversrArr<Rest>, First]
        : Arr;

查找元素

type Includes<Arr extends unknown[], FindItem> =
    Arr extends [infer First, ...infer Rest]
        ? IsEqual<First, FindItem> extends true
            ? true
            : Includes<Rest, FindItem>
        : false;
type IsEqual<A, B> =
    (A extends B ? true : false) & (B extends A ? true : false);

删除元素

type RemoveItem<
    Arr extends unknown[],
    Item,
    Result extends unknown[] = []
> = Arr extends [infer First, ...infer Rest] 
    ? IsEqual<First, Item> extends true
        ? RemoveItem<Rest, Item, Result>
        : RemoveItem<Rest, Item, [...Result, First]>
    : Result;
type IsEqual<A, B> =
    (A extends B ? true : false) & (B extends A ? true : false);

构造指定类型的数组

type BuildArray<
    Length extends number,
    Ele = unknown,
    Arr extends unknown[] = []
> = Arr['length'] extends Length
    ? Arr
    : BuildArray<Length, Ele, [...Arr, Ele]>;

字符串类型的递归

替换子串

type ReplaceAll<
    Str extends string,
    From extends string,
    To extends string,
> = Str extends `${infer Left}${From}${infer Right}`
    ? `${Left}${To}${ReplaceAll<Right, From, To>}`
    : Str;

提取字符做联合类型

type StringToUnion<Str extends string> = 
    Str extends `{infer First}${infer Rest}`
        ? First | StringToUnion<Rest>
        : never;

反转字符串

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

对象类型的递归

深度递归

type DeepToReadonly<T extends Record<string, any>> =
    T extends any
        ? {
           readonly [Key in keyof T]:
               T[Key] extends Object
                   ? T[Key] extends Function
                        ? T[Key]
                        : DeepToReadonly<T[Key]>
                    : T[Key]
        }
        : never;

数组长度做计算

数组长度实现加减乘除

加法

type BuildArray<
    Length extends number,
    Ele = unknown,
    Arr extends unknown[] = [],
> = Arr['length'] extends Length
    ? Arr
    : BuildArray<Length, Ele, [...Arr, Ele]>;

type Add<Num1 extends number, Num2 extends number> =
    [...BuildArray<Num1>, ...BuildArray<Num2>]['length'];

减法

type Subtract<Num1 extends number, Num2 extends number> =
    BuildArray<Num1> extends [...BuildArray<Num2>, ...infer Rest]
        ? Rest['length']
        : never;

乘法

type Multiple<
    Num1 extends number,
    Num2 extends number,
    ResultArr extends unknown[] = [],
> = Num2 extends 0
    ? ResultArr['length']
    : Multiple<Num1, Subtract<Num2, 1>, [...ResultArr, ...BuildArray<Num1>]>;

除法

type Divide<
    Num1 extends number,
    Num2 extends number,
    ResultArr extends unknown[] = [],
> = Num1 extends 0
    ? ResultArr['length']
    : Divide<Subtract<Num1, Num2>, Num2, [...ResultArr, unknown]>;

数组长度实现计数

计算字符串长度

type StrLen<
    Str extends string,
    CountArr extends unknown[] = [],
> = Str extends `${string}${infer Rest}`
    ? StrLen<Rest, [...CountArr, unknown]>
    : CountArr['length']

比较2个数值谁更大

type GreaterThan<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = [],
> = Num1 extends Num2
    ? false
    : CountArr['length'] extends Num2
        ? true
        : CountArr['length'] extends Num1
            ? false
            : GreaterThan<Num1, Num2, [...CountArr, unknown]>;

实现斐波那契数列

type FibonacciLoop<
    PrevArr extends unknown[], 
    CurrentArr extends unknown[], 
    IndexArr extends unknown[] = [], 
    Num extends number = 1
> = IndexArr['length'] extends Num
    ? CurrentArr['length']
    : FibonacciLoop<CurrentArr, [...PrevArr, ...CurrentArr], [...IndexArr, unknown], Num> 

type Fibonacci<Num extends number> = FibonacciLoop<[1], [], [], Num>;

聚合分散可简化

分布式条件类型

当类型参数为联合类型,并且在条件类型左边直接引用该类型参数的时候,TypeScript 会把每一个元素单独传入来做类型运算,最后再合并成联合类型,这种语法叫做分布式条件类型。

type Union = 'a' | 'b' | 'c';
type UppercaseA<Item extends string> =
    Item extends 'a' ? Uppercase<Item> : Item;

这和联合类型遇到字符串时的处理一样:

type Union = 'a' | 'b';
type str = `${Union}~`
// type str = 'a~' | 'b~';

数组转联合类型

type Arr = ['a', 'b', 'c'];
type UnionArr = Arr[number]; 
// type UnionArr = 'a' | 'b' | 'c';

判断是否是联合类型

type isUnion<A, B = A> =
    A extends A
        ? [B] extends [A]
            ? false
            : true
        : never;

当 A 是联合类型时:

  • A extends A 这种写法是为了触发分布式条件类型,让每个类型单独传入处理的,没别的意义。
  • A extends A 和 [A] extends [A] 是不同的处理,前者是单个类型和整个类型做判断,后者两边都是整个联合类型,因为只有 extends 左边直接是类型参数才会触发分布式条件类型。

BEM

type BEM<
    Block extends string,
    Element extends string[],
    Modifiers extends string[]
> = `${Block}__${Element[number]}--${Modifiers[number]}`

全组合

// A 和 B 的全组合
type Combination<A extends string, B extends string> = 
    A | B | `${A}${B}` | `${B}${A}`
    
// 全组合
type AllCombinations<A extends string, B extends string = A> =
    A extens A
        ? Combination<A, AllCombinations<Exclude<B, A>>>
        : never;

特殊特性要记清

IsAny

any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any。

type IsAny<T> = 'a' extends (1 & T) ? true : false;

IsEqual

// 以下这种写法不能判断 isAny,isEqual<'a', any> 会返回 true
type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false);

// 以下这个可以判断 IsEqual2<'a', any> 会返回 false
type IsEqual2<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
    ? true : false;

IsUnion

type IsUnion<A, B> = 
    A extends A
        ? [B] extends [A]
            ? false
            : true
        : never;

IsNever

never 在条件类型中也比较特殊,如果条件类型左边是类型参数,并且传入的是 never,那么直接返回 never。

type TestNever<T> = T extends number ? 1 : 2;
// 如下会返回 never
type result = TestNever<never>; 

// 正确的 IsNever
type IsNever<T> = [T] extends [never] ? true : false;

除此之外,any 在条件类型中也比较特殊,如果类型参数为 any,会直接返回 trueType 和 falseType 的合并。

type TestAny<T> = T extends number ? 1 : 2;
// 如下会返回 1 | 2
type result = TestAny<any>

IsTuple

元组类型的 length 是数字字面量,而数组的 length 是 number。

type IsTuple<T> =
    T extends [...infer Eles]
        ? NotEqual<Ele['length'], number>
        : false;

type NotEqual<A, B> =
    (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
    ? false : true;

UnionToIntersection

联合类型转交叉类型。

type UnionToIntersecion<U> =
  (U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown ? R : never;

GetOptional

提取索引类型中的可选索引。

type GetOptional<Obj extends Record<string, any>> = {
    [
        Key in keyof Obj
            as {} extends Pick<Obj, Key> ? Key : never
    ] : Obj[Key];
}

// Pick 是 TS 内置高级类型
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }

GetRequired

提取索引类型中的非可选索引构造成新的索引类型。

type GetRequired<Obj extends Record<string, any>> = {
    [
        Key in keyof Obj
            as {} extends Pick<Obj, Key> ? never : Key
    ] : Obj[Key];
}

RemoveIndexSignature

过滤掉索引类型中的可索引签名,构造成一个新的索引类型。
索引签名的特性:索引签名不能构造成字符串字面量类型,因为它没有名字,而其他索引可以

type RemoveIndexSignature<Obj extends Record<string, any>> = {
    [
        Key in keyof Obj
            as Key extends `${infer Str}` ? Str : never
    ]: Obj[Key]
}

ClassPublicProps

过滤 class 的 public 属性。
根据特性:keyof 只能拿到 class 的 public 索引,private 和 protected 的索引会被忽略。

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

as const

TypeScript 默认推导出来的类型并不是字面量类型。

const obj = {
    a: 1,
    b: 2,
}
type objType = typeof obj;
// type objType = {
//     a: number;
//     b: number
// }

如果想要推到出字面量,就需要用 as const:

const arr = [1, 2, 3];
type arrType = typeof arr;
// type arrType = number[];

const arr2 = [1, 2, 3] as const;
type arrType2 = typeof arr2;
// type arrType2 = readonly [1, 2, 3];

反转3个元素的元组类型,需要加上 readonly 才能匹配成功。

type ReverseArr<Arr> =
    Arr extends readonly [infer A, infer B, infer C]
    ? [C, B, A]
    : never;

练一练

实现 ParseQueryString

将 'a=1&b=2&c=3' 转成 {a: 1, b: 2, c: 3}

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

type ParseParams<Param extends string> =
    Param extends `${infer Key}=${infer Value}`
        ? {
            [K in Key]: Value
        } : Record<string, any>;

type MergeParams<
    OneParam extends Record<string, any>,
    OtherParam extends Record<string, any>,
> = {
    [Key in keyof OneParam | keyof OtherParam]:
        Key extends keyof OneParam
            ? Key extends keyof OtherParam
                ? MergeValue<OneParam[Key], OtherParam[Key]>
                : OneParam[Key]
            : Key extends keyof OtherParam
                ? OtherParam[Key]
                : never
}

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

TS 内置的高级类型

Parameters

提取函数类型的参数类型

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

ReturnType

提取函数类型的返回值类型

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

ConstructorParameters

提取构造函数的参数类型

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

InstanceType

提取构造器返回值类型

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

ThisParameterType

提取函数参数中 this 的类型

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

OmitThisParameter

去除函数参数中的 this 类型,并且返回一个新的类型

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

Partial

把索引类型的所有索引变成可选类型

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

Required

把索引类型里可选索引改成必选索引

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

Readonly

索引类型的索引添加只读

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

Pick

过滤出指定的索引类型

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

Record

创建索引类型

keyof any 会返回 string | number | symbol

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

如果 Record 里的第一个参数是 string | number | symbol,那么创建的就是索引签名索引类型:

type RecordRes = Record<string, number>;
// RecordRes = {
//     [x: string]: number;
// }

Exclude

去掉联合类型中的某些类型,即取差集

联合类型当作为类型参数出现在条件类型左边时,会被分散成单个类型传入,这叫做分布式条件类型。

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

Extract

提取联合类型中的某些类型,即取交集

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

Omit

去掉某部分索引类型的索引构成新索引类型

type Omit<T, K in keyof any> = Pick<T, EXclude<keyof T, K>>;

Awaited

提取 Promise 的返回值类型

type Awaited<T> = 
    T extends null | undefined
        ? T
        : T extends object & { then(onfulfilled: infer F): any}
            ? F extends ((value, infer V, ...ars: any) => any)
                ? Awaited<V>
                : never
            : T;

NonNullable

判断是否是空类型,即不是 null 或 undefined

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

Uppercase、Lowercase、Capitalize、Uncapitalize

这几个类型分别是实现大写、小写、首字母大写、去掉首字母大写的。
他们的实现是直接用 js 实现的。

综合实战

KebabCaseToCamelCase

'aa-bb-cc' 这种是 KebabCase,而 'aaBbCc' 这种是 CamelCase

type KebabCaseToCamelCase<Str extends string> =
    Str extends `${infer Item}-${infer Rest}`
        ? `${Item}${KebabCaseToCamelCase<Capitalize<Rest>>}`
        : Str;

CamelCaseToKebabCase

type CamelCaseToKebabCase<Str extends string> =
    Str extends `${infer First}${infer Rest}`
        ? First extends Lowercase<First>
            ? `${First}${CamelCaseToKebabCase<Rest>}`
            : `-${Lowercase<First>}${CamelCaseToKebabCase<Rest>}`
        : Str;

Chunk

对数组做分组,比如 1、2、3、4、5 的数组,每两个为 1 组,那就可以分为 1、2 和 3、4 以及 5 这三个 Chunk。

type Chunk<
    Arr extends unknown[],
    ItemLen extends number,
    CurItem extends unknown[] = [],
    Res extends unknown[] = []
> = Arr extends [infer First, ...infer Rest]
    ? CurItem['length'] extends ItemLen
        ? Chunk<Rest, ItemLen, [First], [...Res, CurItem]>
        : Chunk<Rest, ItemLen, [...CurItem, First], Res>
    : [...Res, CurItem]

TupleToNestedObject

根据数组类型,比如 ['a', 'b', 'c'] 的元组类型,再加上值的类型 'xxx',构造出这样的索引类型:

{
    a: {
        b: {
            c: 'xxx'
        }
    }
}
type TupleToNestedObject<
    Tuple extends unknown[],
    ValueType
> = Tuple extends [infer First, ...infer Rest]
    ? {
        [Key in First as Key extends keyof any ? Key : never]:
            Rest extends unknown[]
                ? TupleToNestedObject<Rest, ValueType>
                : ValueType
    }
    : ValueType;

PartialObjectPropByKeys

把一个索引类型的某些 Key 转为 可选的,其余的 Key 不变。

type PartialObjectPropByKeys<
    Obj extends Record<string, any>,
    Key extends keyof any
> = Partial<Pick<Obj, Extract<keyof Obj, Key>>> & Omit<Obj, Key>

type PartialObjectPropByKeys2<
    Obj extends Record<string, any>,
    KeyType extends keyof any
> = {
    [Key in keyof Obj as Key extends KeyType ? Key? : Key]:  Obj[Key]
}

函数重载的三种写法

第一种

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any) {
    return a + b;
}

第二种

interface Func {
    (a: number, b: number): number;
    (a: string, b: string): string;
}
const add: Func = (a: any, b: any) => a + b;

第三种

type Func = ((a: number, b: number) => number) & ((a: string, b: string): string)
const add: Func = (a: any, b: any) => a + b;

UnionToTuple

将联合类型转成元组。

type UnionToTuple<T> = 
    UnionToIntersection<
        T extends any ? () => T : never
    > extends () => infer ReturnType
        ? [...UnionToTuple<Exclude<T, ReturnType>>, ReturnType]
        : [];
        
// 联合转交叉
type UnionToIntersection<U> =
  (U extends U ? (x: U) => unknown : never)  extends (x: infer R) => unknown ? R : never;

join

实现一个类似的效果,将:

const res = join('-')('guang', 'and', 'dong');
// 转成 res = 'guang-and-dong'

join 代码实现

declare function join<
    Delimiter extends string
>(delimiter: Delimiter):
    <Items extends string[]>
        (...parts: Items) => JoinType<Items, Delimiter>;

type JoinType<
    Items extends any[],
    Delimiter extends string,
    Result extends string = ''
> = Items extends [infer First, ...infer Rest]
    ? JoinType<Rest, Delimiter, `${Result}${First & string}-`>
    : RemoveLastDelimiter<Result>;

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

AllKeyPath

拿到一个索引类型的所有 key 的路径。

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]

Defaultize

实现这样一个高级类型,对 A、B 两个索引类型做合并,如果是只有 A 中有的不变,如果是 A、B 都有的就变为可选,只有 B 中有的也变为可选。

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>>>

infer extends

枚举值转联合类型

以下会把枚举的数值类型转成字符串类型。

enum Code {
    a = 111,
    b = 222,
    c = 'abc'
}
type res = `${Code}`
// res = '111' | '222' | 'abc'

StrToNum

使用 infer extends 后就就可以正常使用了。

enum Code {
    a = 111,
    b = 222,
    c = 'abc'
}
type StrToNum<Str> = Str extends `${infer Num extends number}`
    ? Num
    : Str;
type res = StrToNum<`${Code}`>
// res = 'abc' | 111 | 222

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions