英文:
Typescript having problem with getting correct type while filtering some types from union type
问题
我有类似以下代码的代码:
type Foo = {
products:
| {
fooName: string;
}
| {
fooName: string;
}[]
| null;
amount: number;
}[];
const exampleObj = { products: { fooName: 'Foo' }, amount: 13 };
const foo: Foo = [
exampleObj,
exampleObj,
{ products: [{ fooName: 'Foo' }, { fooName: 'Foo' }], amount: 14 },
] as Foo;
const bar = foo.map((el) => {
if (!el.products || Array.isArray(el.products)) {
return exampleObj;
}
return el;
});
bar.forEach((el) => {
console.log(el.products.fooName);
});
这段代码按预期工作 - 在对foo
进行映射时,将具有设置为数组的产品值的元素过滤掉。Bar 的第三个元素设置为 exampleObj。
我的问题是 TypeScript 建议 bar 的类型为:
const bar: {
products: {
fooName: string;
} | {
fooName: string;
}[] | null;
amount: number;
}[]
并且我收到一个错误:
Property 'fooName' does not exist on type '{ fooName: string; } | { fooName: string; }[]'.
Property 'fooName' does not exist on type '{ fooName: string; }[]'.
我可以通过使用以下方式将 bar 强制转换来解决此错误:
const bar = foo.map((el) => {
if (!el.products || Array.isArray(el.products)) {
return exampleObj;
}
return el;
}) as typeof exampleObj[];
但我是否遗漏了什么,还是 TypeScript 只是不能理解代码中发生的情况?
英文:
I have code similar to this:
type Foo = {
products:
| {
fooName: string;
}
| {
fooName: string;
}[]
| null;
amount: number;
}[];
const exampleObj = { products: { fooName: 'Foo' }, amount: 13 };
const foo: Foo = [
exampleObj,
exampleObj,
{ products: [{ fooName: 'Foo' }, { fooName: 'Foo' }], amount: 14 },
] as Foo;
const bar = foo.map((el) => {
if (!el.products || Array.isArray(el.products)) {
return exampleObj;
}
return el;
});
bar.forEach((el)=> {
console.log(el.products.fooName)
})
this code works as expected - element in foo that has product value set to array is filtered out while mapping through foo. Bar has third element set to exampleObj.
My problem is that typescript suggests type of bar is:
const bar: {
products: {
fooName: string;
} | {
fooName: string;
}[] | null;
amount: number;
}[]
and I get an error:
Property 'fooName' does not exist on type '{ fooName: string; } | { fooName: string; }[]'.
Property 'fooName' does not exist on type '{ fooName: string; }[]'.
I can fix this error by casting bar with
const bar = foo.map((el) => {
if (!el.products || Array.isArray(el.products)) {
return exampleObj;
}
return el;
}) as typeof exampleObj[]
but am I missing something or typescript just can't understand what is going on in that code?
Codepen: https://codepen.io/Neidz/pen/qBJyjWa (but as far as I know you will not get type suggestions or typescript errors here)
答案1
得分: 1
您遇到了 TypeScript 的一个缺失特性,如 microsoft/TypeScript#42384 中所请求的。 TypeScript 目前不会将对象属性的 narrowing 结果传播到父对象。(除非父对象属于判别联合类型,但 Foo[number]
只是单个对象类型,根本不是联合类型,因此绝对不是判别联合。)因此,检查 el.products
属性只会将 el.products
缩小范围,而不是 el
。
如果您希望看到此功能实现,去提个 👍 吧。这可能不会 太有帮助。
无论如何,在我们获得对该功能的支持之前,您需要解决此问题。一种方法是将缩小的属性复制到新对象中,因为这将强制编译器分析将缩小的类型应用于新对象中的属性:
const bar = foo.map((el) => {
if (!el.products || Array.isArray(el.products)) {
return exampleObj;
}
const ret = { ...el, products: el.products };
/* const ret: {
products: {
fooName: string;
};
amount: number; } */
return ret;
});
bar.forEach((el) => {
console.log(el.products.fooName); // okay
})
或者,稍微更符合人体工程学的方式:
const bar = foo.map((el) => {
const { products, ...restEl } = el;
if (!products || Array.isArray(products)) {
return exampleObj;
}
return { ...restEl, products }
});
bar.forEach((el) => {
console.log(el.products.fooName); // okay
});
英文:
You've run into a missing feature of TypeScript, as requested in microsoft/TypeScript#42384. TypeScript doesn't currently propagate the results of narrowing an object property up to the parent object. (Well, except if that parent object is of a discriminated union type, but Foo[number]
is just a single object type, not a union at all, and therefore definitely not a discriminated union.) So checking el.products
property only serves to narrow el.products
and not el
.
If you want to see this feature implemented, it wouldn't hurt for you to go to it and give a 👍. It probably wouldn't help much either, though.
Anyway, until and unless we get support for the feature, you'll need to work around it. One way to do so is to copy the narrowed property over into a new object, since that will force the compiler to go through the analysis of the applying the narrowed type to the property in the new object:
const bar = foo.map((el) => {
if (!el.products || Array.isArray(el.products)) {
return exampleObj;
}
const ret = { ...el, products: el.products };
/* const ret: {
products: {
fooName: string;
};
amount: number; } */
return ret;
});
bar.forEach((el) => {
console.log(el.products.fooName); // okay
})
Or, a little more ergonomically:
const bar = foo.map((el) => {
const { products, ...restEl } = el;
if (!products || Array.isArray(products)) {
return exampleObj;
}
return { ...restEl, products }
});
bar.forEach((el) => {
console.log(el.products.fooName); // okay
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论