如何基于type与multiple为Record值推导并限制TypeScript类型?
基于type和multiple限制函数参数Record类型的解决方案
需求描述
需要为useQuery函数的参数创建类型,使其能根据type字符串和可选的multiple布尔值,自动限制ref和whitelist的类型。示例用法如下:
useQuery({ a: { ref: ref(''), // ref类型为Ref<string> type: 'string', }, b: { whitelist: [1, 2, 3], // whitelist类型为number[](可选) ref: ref([1, 2, 3]), // ref类型为Ref<number[]> type: 'number', multiple: true, }, });
尝试过的方案及问题
- 联合类型方案:无法正确处理可选的
multiple参数,当不传入multiple时,ref的类型会被推断为string | string[]这类联合类型,不符合预期:
type DumbType = { whitelist?: string[]; ref: Ref<string>; type: 'string'; } | { whitelist?: string[]; ref: Ref<string[]>; type: 'string'; multiple: true; } | { whitelist?: number[]; ref: Ref<number>; type: 'number'; } | { whitelist?: number[]; ref: Ref<number[]>; type: 'number'; multiple: true; }; // 问题:不传入multiple时,ref被推断为string | string[] useQuery({ type: 'string', ref: ref(), })
- 映射类型方案:
QueryOptions单独使用正常,但UseQueryOptions无法正确推断Record值的泛型参数,出现类型错误:
type Primitives = 'string' | 'number' | 'boolean'; export type UseQueryOptions<T> = { [K in keyof T]: QueryOptions<T[K]['type'], T[K]['multiple']>; // 此处报错 }; export type QueryOptions<T extends Primitives, M extends boolean | undefined> = { whitelist?: StringTypeToType<T, true>; ref: Ref<StringTypeToType<T, M>>; type: T; multiple?: M; }; export type StringTypeToType<P extends Primitives, M extends boolean | undefined> = P extends 'string' ? ArrayWhenMultiple<M, string> : P extends 'number' ? ArrayWhenMultiple<M, number> : P extends 'boolean' ? ArrayWhenMultiple<M, boolean> : never; export type ArrayWhenMultiple<M extends boolean | undefined, T = any> = M extends true ? T[] : T; export type TypeToRefType<P extends Primitives, M extends boolean> = Ref<StringTypeToType<P, M>>; export const useQuery = <P extends Primitives, m extends boolean | undefined>(options: QueryOptions<P, M>) => {};
解决方案
调整泛型约束和类型映射逻辑,让TypeScript能正确推断每个属性的类型:
// 假设Ref是Vue的Ref类型,这里定义简化版 type Ref<T> = { value: T }; const ref = <T>(val?: T): Ref<T | undefined> => ({ value: val }); type Primitives = 'string' | 'number' | 'boolean'; // 基础类型映射:将字符串类型转为TS原生类型 type PrimitiveToType<T extends Primitives> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : never; // 根据multiple决定是否转为数组类型 type TypeWithMultiple<T extends Primitives, M extends boolean | undefined> = M extends true ? PrimitiveToType<T>[] : PrimitiveToType<T>; // 单个字段的配置类型 type QueryFieldOptions<T extends Primitives, M extends boolean | undefined> = { type: T; multiple?: M; ref: Ref<TypeWithMultiple<T, M>>; whitelist?: PrimitiveToType<T>[]; // whitelist始终是对应类型的数组 }; // 允许传入多个字段的Record类型 type UseQueryOptions = Record<string, QueryFieldOptions<Primitives, boolean | undefined>>; // 调整useQuery函数,让TS能自动推断每个字段的泛型参数 export const useQuery = <T extends UseQueryOptions>(options: T) => {}; // 测试示例 useQuery({ a: { ref: ref(''), // 正确推断为Ref<string> type: 'string', }, b: { whitelist: [1, 2, 3], // 正确推断为number[] ref: ref([1, 2, 3]), // 正确推断为Ref<number[]> type: 'number', multiple: true, }, c: { ref: ref(true), // 正确推断为Ref<boolean> type: 'boolean', }, d: { ref: ref([true, false]), // 正确推断为Ref<boolean[]> type: 'boolean', multiple: true, }, });
关键调整点
- 拆分类型映射:将
PrimitiveToType和TypeWithMultiple拆分,简化嵌套条件判断,让类型逻辑更清晰。 - Record类型适配:
UseQueryOptions直接定义为Record<string, QueryFieldOptions<...>>,配合函数泛型T extends UseQueryOptions,让TS自动推断每个字段的具体类型。 - 函数泛型优化:不再手动指定
P和M泛型,而是通过传入的options自动推断每个字段的type和multiple对应的类型。
内容的提问来源于stack exchange,提问作者JoCa
相关产品推荐
相关产品推荐

