TypeScript泛型Hook函数useFilters的类型推断问题
泛型Hook useFilters类型推断问题修复方案
问题原因
- 泛型参数设计缺陷:原泛型仅捕获key的字符串类型,未关联每个key对应的
type和multi属性,导致FilterType无法精准推导每个字段的类型。 - 工具函数类型缺失:
mapSingleValue和mapMultiValue未定义精确的返回类型,TypeScript无法根据输入的type参数推断正确的返回值类型,进而污染filters的类型推断结果。 - setFilterByKey参数类型宽泛:参数类型依赖未绑定具体属性的泛型
K,无法约束每个key对应的正确值类型。
修复方案
1. 重构类型定义
调整FilterType,利用联合类型的索引访问和条件类型,精准映射每个过滤键的对应值类型:
type FilterValueType = 'string' | 'number'; type FilterKeyDefinition<K extends string> = { key: K; type: FilterValueType; multi: boolean; }; type FilterValue<T extends string | number> = T | undefined | null; // 基于过滤键定义联合类型生成对应的filters对象类型 type FilterType<FilterKeys extends FilterKeyDefinition<string>> = { [K in FilterKeys['key']]?: FilterKeys extends { key: K; multi: true; type: infer T } ? T extends 'number' ? FilterValue<number>[] : FilterValue<string>[] : FilterKeys extends { key: K; type: infer T } ? T extends 'number' ? FilterValue<number> : FilterValue<string> : never; };
2. 为工具函数添加精确返回类型
通过条件类型让TypeScript根据type参数推断正确的返回值:
const getNumberValue = (value: FilterValue<string>): FilterValue<number> => Number.isNaN(Number(value)) ? undefined : Number(value); const mapSingleValue = <T extends FilterValueType>( value: FilterValue<string>, type: T ): T extends 'number' ? FilterValue<number> : FilterValue<string> => { if (type === 'number') { return getNumberValue(value) as any; } return value as any; }; const mapMultiValue = <T extends FilterValueType>( values: FilterValue<string>[], type: T ): T extends 'number' ? FilterValue<number>[] : FilterValue<string>[] => { if (type === 'number') { return values.map(getNumberValue).filter(v => v != null) as any; } return values.filter(v => v != null) as any; };
3. 重构useFilters泛型
让泛型捕获完整的过滤键定义数组,而非单独的key类型,确保类型推断精准:
import { useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; const useFilters = <FilterKeys extends readonly FilterKeyDefinition<string>[]>( filterKeys: FilterKeys ) => { const [searchParams, setSearchParams] = useSearchParams(); const filters = useMemo((): FilterType<FilterKeys[number]> => { return filterKeys.reduce((prev, { key, type, multi }) => { let value: FilterType<FilterKeys[number]>[typeof key]; if (multi) { value = mapMultiValue(searchParams.getAll(key), type) as any; } else { value = mapSingleValue(searchParams.get(key), type) as any; } return { ...prev, [key]: value }; }, {} as FilterType<FilterKeys[number]>); }, [searchParams, filterKeys]); const setFilterByKey = <K extends FilterKeys[number]['key']>( key: K, value: FilterType<FilterKeys[number]>[K] ) => { const newParams = new URLSearchParams(searchParams); if (value == null) { newParams.delete(key); } else if (Array.isArray(value)) { newParams.delete(key); value.forEach(v => v != null && newParams.append(key, String(v))); } else { newParams.set(key, String(value)); } setSearchParams(newParams); }; return { setFilterByKey, filters }; };
4. 使用示例
const CUSTOM_FILTER_KEYS = [{ key: 'status', type: 'string', multi: true, }, { key: 'price', type: 'number', multi: false, }, ] as const; // 类型自动推断: // filters: { status?: FilterValue<string>[]; price?: FilterValue<number> } // setFilterByKey: (key: 'status' | 'price', value: 对应类型) => void const { filters, setFilterByKey } = useFilters(CUSTOM_FILTER_KEYS);
关键说明
- 泛型
FilterKeys捕获完整的过滤键定义数组,确保TypeScript能获取每个key对应的type和multi属性。 FilterType通过条件类型遍历联合类型中的每个key,精准映射对应的value类型。setFilterByKey新增了对空值和数组值的处理,符合URLSearchParams的操作规范,同时类型约束更严格。
内容的提问来源于stack exchange,提问作者Kainar Masujima
相关产品推荐
相关产品推荐

