如何编写使用键数组从另一种类型中提取属性的 TypeScript 类型

How to write a TypeScript type that extracts properties from another type using an array of keys

我正在尝试编写一个 TypeScript 类型,将一个类型的键元组转换为仅具有这些属性的类型。以下代码是我得到的最接近的代码:

type Primitive = string | number | boolean;
type ExtractProps<TObject, TKeys extends (keyof TObject)[], TExtends = any> = {
    [Index in keyof TKeys as TKeys[Index] extends keyof TObject ? (TObject[TKeys[Index]] extends TExtends ? TKeys[Index] : never) : never ]:
        TKeys[Index] extends keyof TObject ? TObject[TKeys[Index]] : never 
};

type Type = {
    'string': string;
    'number': number;
    'boolean': boolean;
    'array': Array<any>
}

type Test1 = ExtractProps<Type, ['string', 'number', 'array']>;
// Output:
// type Test1 = {
//     string: string | number | any[];
//     number: string | number | any[];
//     array: string | number | any[];
// }
// Desired Output:
// type Test1 = {
//     string: string;
//     number: number;
//     array: any[];
// }
type Test2 = ExtractProps<Type, ['string', 'number', 'array'], Primitive>;
// Output:
// type Test2 = {
//     string: string;
//     number: number;
// }
// Desired Output:
// type Test2 = {
//     string: string;
//     number: number;
// }
type Test3 = ExtractProps<Type, ['string', 'number'], Primitive>;
// Output:
// type Test3 = {
//     string: string | number;
//     number: string | number;
// }
// Desired Output:
// type Test3 = {
//     string: string;
//     number: number;
// }

只有当重新映射表达式对其中一个键的计算结果为 never 时,我才能使该类型正常工作(如 Type2 中的 array 和 属性 类型一样不扩展 Primitive)。有没有一种写法 ExtractProps 使得 Type1 和 Type3 有正确的 属性 类型?

你找的好像是the Pick<T, K> utility type which produces a type from T with only a subset of the known properties, specifically those whose keys are in K, and something we can call PickByValue<T, V> (as requested in ) which produces a type from T with only a subset of the known properties, specifically those whose values are in V. We can use key remapping via as的组合写成PickByValue:

type PickByValue<T, V> =
    { [K in keyof T as T[K] extends V ? K : never]: T[K] };

然后合并得到ExtractProps:

type ExtractProps<T, K extends keyof T, V = any> =
    PickByValue<Pick<T, K>, V>

我们来测试一下:

type Test1 = ExtractProps<Type, 'string' | 'number' | 'array'>;
/* type Test1 = {
    string: string;
    number: number;
    array: any[];
} */

type Test2 = ExtractProps<Type, 'string' | 'number' | 'array', Primitive>;
/* type Test2 = {
    string: string;
    number: number;
} */


type Test3 = ExtractProps<Type, 'string' | 'number', Primitive>;
/* type Test3 = {
    string: string;
    number: number;
} */

看起来不错。


请注意,您也可以通过组合 PickPickByValue 的功能而不使用它们来直接实现 ExtractProps

type ExtractProps<T, K extends keyof T, V = any> =
    { [P in keyof T as T[P] extends V ? Extract<K, P> : never]: T[P] 
} 

最后,请注意,如问题中所写,您的 ExtractProps 版本希望其第二个参数是 arraylike 类型,其元素是键。你可以做 那:

type ExtractPropsArray<T, KS extends Array<keyof T>, V = any> =
    PickByValue<Pick<T, KS[number]>, V>;

但实现不关心数组类型的细节,只关心其元素类型的并集。它只是丢弃了关于 KS 的大部分信息。我不建议编写一个需要它实际上不需要的信息的类型。你总是可以在使用数组之前解包它,比如 ExtractProps<T, KS[number], V>,而当你从类似键的类型开始时发明一个无用的数组包装器有点愚蠢,比如 ExtractPropsArray<T, K[], V>ExtractPropsArray<T, [K, K], V>。但这最终更多的是一种意见,而不是权威来源,所以你应该按照你认为合适的方式去做。

Playground link to code