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

TypeScript泛型Hook函数useFilters的类型推断问题

泛型Hook useFilters类型推断问题修复方案

问题原因

  1. 泛型参数设计缺陷:原泛型仅捕获key的字符串类型,未关联每个key对应的type和multi属性,导致FilterType无法精准推导每个字段的类型。
  2. 工具函数类型缺失:mapSingleValue和mapMultiValue未定义精确的返回类型,TypeScript无法根据输入的type参数推断正确的返回值类型,进而污染filters的类型推断结果。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 17:14:56