修复React useQueryStates泛型深层类型推导问题
修复React useQueryStates Hook的深层泛型类型推导问题
问题说明
在React中实现useQueryStates Hook时,泛型仅能推导一层类型,导致枚举数组、字符串字面量等深层类型被自动拓宽为string或string[],无法保留原始的精确类型信息。
示例代码
const [{ view }, setQueryStates] = useQueryStates({ view: { defaultValue: 'gantt', enum: ['gantt', 'kanban'], }, });
当前类型推导结果
export default function useQueryStates<{ view: { defaultValue: string; enum: string[]; }; }>(queries: {view: {defaultValue: string, enum: string[]}}): QueryStateResponse<{view: {defaultValue: string, enum: string[]}}>
期望类型推导结果
export default function useQueryStates<{ view: { defaultValue: 'gantt'; enum: ['gantt', 'kanban']; }; }>(queries: {view: {defaultValue: "gantt", enum: ["gantt", "kanban"]}}): QueryStateResponse<{view: {defaultValue: "gantt", enum: ["gantt", "kanban"]}}>
当前类型定义
type QueryDefinition = | string | { value?: string; defaultValue?: string; enum?: string[]; }; type QueryStateParams = { [key: string]: QueryDefinition; }; type ExtractQueryValue<T extends QueryDefinition> = T extends string ? string : T extends { value?: infer V; defaultValue?: infer D; enum?: (infer E)[] } ? E extends string ? (V extends string ? V : never) | (D extends string ? D : never) | (E extends string ? E : never) | '' : never : string; type QueryState<Params extends QueryStateParams> = { [P in keyof Params]: ExtractQueryValue<Params[P]>; }; type SetQueryState<Params extends QueryStateParams> = (updatedState: Partial<QueryState<Params>>) => void; type QueryStateResponse<Params extends QueryStateParams> = [QueryState<Params>, SetQueryState<Params>]; function useQueryStates<QueryParams extends QueryStateParams>(queries: QueryParams): QueryStateResponse<QueryParams>
修复方案
核心问题在于原类型定义中直接使用string和string[]约束,导致TypeScript自动拓宽字面量类型。通过泛型参数保留原始字面量类型,并调整约束逻辑,即可实现深层类型推导:
// 改为泛型定义,允许保留字面量类型 type QueryDefinition<T = string> = | T | { value?: T; defaultValue?: T; enum?: T[]; }; // 调整类型提取逻辑,直接推导字面量类型 type ExtractQueryValue<T extends QueryDefinition> = T extends string ? T | '' : T extends { value?: infer V; defaultValue?: infer D; enum?: (infer E)[] } ? V | D | E | '' : never; type QueryState<Params extends Record<string, QueryDefinition>> = { [P in keyof Params]: ExtractQueryValue<Params[P]>; }; type SetQueryState<Params extends Record<string, QueryDefinition>> = ( updatedState: Partial<QueryState<Params>> ) => void; type QueryStateResponse<Params extends Record<string, QueryDefinition>> = [ QueryState<Params>, SetQueryState<Params> ]; // 改用Record<string, QueryDefinition>作为泛型约束,避免类型拓宽 function useQueryStates<QueryParams extends Record<string, QueryDefinition>>( queries: QueryParams ): QueryStateResponse<QueryParams> { // 此处保留你的Hook实现逻辑 return [] as any; }
修复说明
- 泛型化QueryDefinition:通过泛型参数
T允许类型系统保留传入的字面量类型,而非强制转换为string。 - 移除固定索引签名约束:原
QueryStateParams的索引签名会强制所有属性类型被拓宽为QueryDefinition的基础类型,改用Record<string, QueryDefinition>让TypeScript自动推导每个属性的精确类型。 - 简化ExtractQueryValue逻辑:直接提取推导的字面量类型,无需额外判断是否为
string,避免类型丢失。
经过上述修改,示例代码中的view类型会被精确推导为'gantt' | 'kanban' | '',defaultValue和enum的字面量类型也会被完整保留。
内容的提问来源于stack exchange,提问作者Yossi Shemesh
相关产品推荐
相关产品推荐

