“ B”可以分配给“ T”类型的约束,但是可以以不同的约束'a'来实例化't'。
#javascript #typescript #generics

这个错误困扰着我多年。和thanks to Inigo I finally understood the problem

tl; dr:当您在通用声明中使用T extends A时,您需要T至少为 A,但是它可以具有更多的属性(通过是不同的子类型)。

)。

当您要创建一个符合T的对象时,就会出现问题。您不能,因为T可能需要比A的其他或更具体的属性。

当我们创建处理通用对象和数组的React Hooks时,这种情况发生了很多。

一个解决方案是将回调函数传递给知道如何将A之类的内容转换为T的通用,鉴于您的通用功能期望处理的内容。

代码

假设我们正在为combobox创建一个钩子的抽象过滤选项,如果没有搜索结果:

type SelectOption = {
  name: string;
  value: string | null;
  icon?: string;
  description?: string;
  disabled?: boolean;
};

function useComboboxFilter<T extends SelectOption>(props: {
  options: T[];
  filterValue: string;
}): { filteredOptions: T[] } {
  let filteredOptions = props.filterValue
    ? props.options.filter((option) => option.name.includes(props.filterValue))
    : props.options;

  if (props.filterValue && !filteredOptions.length) {
    filteredOptions = [
      { name: "No results found", value: null, disabled: true },
    ];
  }

  return { filteredOptions };
}

react devs的注释
因为这是一个示例,所以我没有正确优化代码。适当的react钩子应包裹filteredOptions和返回对象:
function useComboboxFilter<T extends SelectOption>(props: {
  options: T[];
  filterValue: string;
}): { filteredOptions: T[] } {
  const filteredOptions = useMemo(() => {
    let filteredOptions = props.filterValue
      ? props.options.filter((option) =>
          option.name.includes(props.filterValue)
        )
      : props.options;

    if (props.filterValue && !filteredOptions.length) {
      filteredOptions = [
        { name: "No results found", value: null, disabled: true },
      ];
    }

    return filteredOptions;
  }, [props.filterValue, props.options]);

  return useMemo(() => ({ filteredOptions }), [filteredOptions]);
}

问题

如果您check this code with the TypeScript compiler,您将获得此错误:

Type '{ name: string; value: null; disabled: true; }'
is not assignable to type 'T'.
  '{ name: string; value: null; disabled: true; }' is 
  assignable to the constraint of type 'T', but 'T' could
  be instantiated with a different subtype of constraint
  'SelectOption'. ts(2322)

这是一个隐秘的错误消息:它在不帮助读者了解情况或实际上做错的事情的情况下正确地描述了问题。

这里的问题很简单:{ name: string; value: null; disabled: true; }满足SelectOption,但是由于T可能是SelectOption的不同子类型,也就是说,一种具有略有不同需求的类型,因此您的通用函数不知道 create create create create>它的新版本

我们示例的一个明显问题是:想象我们创建的通用类型T需要一个id属性:

type SelectOptionWithId = {
  id: string;
} & SelectOption;

const myFilter = useComboboxFilter<SelectOptionWithId>({
  options: [],
  filterValue: "bla",
});

在这种情况下,myFilter的结果将具有一个没有id 的对象,因为通用挂钩不知道它需要添加该属性。

由于通用代码不知道T,因此您只能创建一个新版本的SelectOption。为了解决此问题,您需要一种将SelectOption对象转换为T的方法,只有您的非生成代码才能提供。

一个解决方案

因为在useComboboxFilter中您只知道SelectOption,而您只关心它的namevaluedisabled属性,因此您可以创建一个必需的函数参数,将部分SelectOption转换为任何t:

function useComboboxFilter<T extends SelectOption>(props: {
  options: T[];
  createOption: (
    option: Pick<SelectOption, "name" | "value" | "disabled">
  ) => T;
  filterValue: string;
}): { filteredOptions: T[] } {
  let filteredOptions = props.filterValue
    ? props.options.filter((option) => option.name.includes(props.filterValue))
    : props.options;

  if (props.filterValue && !filteredOptions.length) {
    filteredOptions = [
      props.createOption({
        name: "No results found",
        value: null,
        disabled: true,
      }),
    ];
  }

  return { filteredOptions };
}

The error is gone!

然后,当您使用SelectOption子类型的通用useComboboxFilter时,您必须确保通用函数可以创建一个满足该类型的新对象:

type SelectOptionWithId = {
  id: string;
} & SelectOption;

const createOption = (option: SelectOption): SelectOptionWithId => ({
  id: Math.random().toString(),
  ...option,
});

const myFilter = useComboboxFilter({
  options: [],
  createOption,
  filterValue: "bla",
});

请注意,使用createOption属性,typeScript Infers SelectOptionWithId for myFilter


如果您想了解有关仿制药和其他整洁的打字稿功能的更多信息,请务必检查@mattpocockukYouTube videos和他的Total TypeScript online course