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