英文:
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<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.
答案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<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>[];
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论