You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修复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;
}

修复说明

  1. 泛型化QueryDefinition:通过泛型参数T允许类型系统保留传入的字面量类型,而非强制转换为string。
  2. 移除固定索引签名约束:原QueryStateParams的索引签名会强制所有属性类型被拓宽为QueryDefinition的基础类型,改用Record<string, QueryDefinition>让TypeScript自动推导每个属性的精确类型。
  3. 简化ExtractQueryValue逻辑:直接提取推导的字面量类型,无需额外判断是否为string,避免类型丢失。

经过上述修改,示例代码中的view类型会被精确推导为'gantt' | 'kanban' | '',defaultValue和enum的字面量类型也会被完整保留。

内容的提问来源于stack exchange,提问作者Yossi Shemesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:25:21