TypeScript泛型中的谓词不起作用?

huangapple go评论72阅读模式
英文:

TypeScript predicates not working with generics?

问题

以下是翻译好的内容:

我创建了 isWeightedItemArray 来确保 items 的类型为 WeightedItem<T>[],但 TypeScript 仍然会抛出错误,似乎要求我在使用 predicate 函数后仍然将 items 强制转换为 WeightedItem<T>[]

为什么在使用谓词函数后,items 的类型是 T[] & WeightedItem<T>[],而不仅仅是 WeightedItem<T>[]

最小可复现示例 这里

英文:

Take a look at the following snippet:

type WeightedItem<T> = { item: T; weight: number };

const isWeightedItemArray = <T>(items: WeightedItem<T>[] | T[]): items is WeightedItem<T>[] => {
  return (items as WeightedItem<T>[]).every((item: WeightedItem<T>) => "weight" in item);
};

export const createWeightedTable = <T>(items: T[]) => {
  if (isWeightedItemArray(items)) {
    // items is typed as  T[] & WeightedItem<T>[], and weight does not exist on type T
    const weights = (items).map((item) => item.weight);
  }
}

I created isWeightedItemArray to guarantee that items is of type WeightedItem<T>[], and yet TypeScript still throws an error and would apparently force me to type cast items to WeightedItem<T>[] even after using the predicate function.

Why is items typed as T[] & WeightedItem<T>[] and not just WeightedItem<T>[] after the predicate function?

Minimal reproducible example here.

答案1

得分: 1

It seems that you want your T and WeightedItem<T> to be separate objects, where {item: T, weight: number} has the item as a property. If that understanding is correct, then the problem is that you have declared items: T[] when logically you want it to be either T[] or WeightedItem<T>[]. If it can't be the latter, then it wouldn't make sense to test it with your isWeightedItemArray function!

So the solution is simply to correct the parameter's type:

export const createWeightedTable = <T>(items: T[] | WeightedItem<T>[]): void => {
  // ...
}

Then the isWeightedItemArray type predicate can correctly narrow the type of items to that branch of the union.

英文:

It seems that you want your T and WeightedItem&lt;T&gt; to be separate objects, where {item: T, weight: number} has the item as a property. If that understanding is correct, then the problem is that you have declared items: T[] when logically you want it to be either T[] or WeightedItem&lt;T&gt;[]. If it can't be the latter then it wouldn't make sense to test it with your isWeightedItemArray function!

So the solution is simply to correct the parameter's type:

export const createWeightedTable = &lt;T&gt;(items: T[] | WeightedItem&lt;T&gt;[]): void =&gt; {
  // ...
}

Then the isWeightedItemArray type predicate can correctly narrow the type of items to that branch of the union.

Playground Link

答案2

得分: 0

I would rewrite it like this - with no generics, as none of those functions actually need the T type that you declare, so you can use unknown there:

type WeightedItem<T> = { item: T; weight: number };

const isWeightedItemArray = (items: WeightedItem<unknown>[] | unknown[]): items is WeightedItem<unknown>[] => {
  return items.every(item => item && typeof item === 'object' && 'weight' in item);
};

export const createWeightedTable = (items: unknown[] | WeightedItem<unknown>[]): void => {
  if (isWeightedItemArray(items)) {
    const weights = items.map(item => item.weight);
  }
}

Note: The createWeightedTable needs rewriting anyway, because the question author added some new requirements in the comments.

Update: And a version with <T> type (thanks to kaya3)

const isWeightedItemArray = <T>(items: WeightedItem<T>[] | unknown[]): items is WeightedItem<T>[] => {
  return items.every(item => item && typeof item === 'object' && 'weight' in item);
};

Here it is more explicit that we verify if that is WeightedItem<T>[].

For simplicity, you could declare types like:

type WeightedArray = WeightedItem<unknown>[];
英文:

I would rewrite it like this - with no generics, as none of those functions actually need the T type that you declare, so you can use unknown there:

type WeightedItem&lt;T&gt; = { item: T; weight: number };

const isWeightedItemArray = (items: WeightedItem&lt;unknown&gt;[] | unknown[]): items is WeightedItem&lt;unknown&gt;[] =&gt; {
  return items.every(item =&gt; item &amp;&amp; typeof item === &#39;object&#39; &amp;&amp; &#39;weight&#39; in item);
};

export const createWeightedTable = (items: unknown[] | WeightedItem&lt;unknown&gt;[]): void =&gt; {
  if (isWeightedItemArray(items)) {
    const weights = (items).map((item) =&gt; item.weight);
  }
}

Note: The createWeightedTable needs rewriting anyway, because the question author added some new requirements in the comments.

Update: And a version with &lt;T&gt; type (thanks to kaya3)

const isWeightedItemArray = &lt;T&gt;(items: WeightedItem&lt;T&gt;[] | unknown[]): items is WeightedItem&lt;T&gt;[] =&gt; {
  return items.every(item =&gt; item &amp;&amp; typeof item === &#39;object&#39; &amp;&amp; &#39;weight&#39; in item);
};

Here it is more explicit that we verify if that is WeightedItem&lt;T&gt;[].

For simplicity, you could declare types like:

type WeightedArray = WeightedItem&lt;unknown&gt;[];

huangapple
  • 本文由 发表于 2023年4月17日 01:29:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76029320.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定