I'm checking with different TypeScript version. TypeScript surprise me a lot of time on how much is smart, but this...?
type Product = { id: number, imageUrl: null | string };
const products: Product[] = [
{ id: 1, imageUrl: 'assets/img/1.jpg' },
{ id: 2, imageUrl: null }
];
products.filter(p => null !== p.imageUrl).forEach(p => {
p.imageUrl; // null | string
});
Am I'm doing something wrong or it's just a limitation of the language?
CodePudding user response:
It's a limitation of the language, but a simple common solution is to use the is
syntax to convey this information downstream
products
.map(
(p: Product) => p.imageUrl
)
.filter(
(imageUrl: string | null): imageUrl is string => !!imageUrl
)
.forEach(
(imageUrl: string) => {
imageUrl; // string
}
);
CodePudding user response:
It is currently a limitation of the language. There are however some open issues in GitHub if you want to follow them: #38390, #16069 You can add a type guard and it should work (as suggested in the referenced issues):
type Product = { id: number, imageUrl: null | string };
const products: Product[] = [
{ id: 1, imageUrl: 'assets/img/1.jpg' },
{ id: 2, imageUrl: null }
];
products.filter((p): p is {id: number, imageUrl: string } => null !== p.imageUrl).forEach(p => {
p.imageUrl; // string
});
CodePudding user response:
This is one of those times you have to work with the language:
type Product = { id: number, imageUrl: null | string };
const products: Product[] = [
{ id: 1, imageUrl: 'assets/img/1.jpg' },
{ id: 2, imageUrl: null }
];
products.forEach(p => {
if (p.imageUrl) {
// here the typechecker knows it's not null
}
});
Note that this is also a more efficient iteration, although that might not matter in your case.
The problem with your original is that filter
is a function from T[] => T[]
, not a function from T[] => SomeSubsetOfTTheCompilerKnowsYouNullChecked[]
.
The compiler can tell in a given scope/block if an access has been checked, and can even do this (in more recent versions) if there's an error path:
function f(x: null | string) {
if (x === null) {
throw new Error('oops');
}
x.repeat(3); // safe!
}
...but it can't do this across function calls:
function g(x: null | string) {
return x;
}
const maybeString = Math.round(Math.random()) ? 'hi' : null;
if (maybeString) {
const a = g(maybeString); // type is still null | string!
}