I am experiencing some weird behaviour in TypeScript (v4.6.2). Why does (a) work but (b) and (c) don't?
const a: string[] | null = []
if (a?.length > 1) {
console.log(1)
}
const b = [] as (string[] | null)
if (b?.length > 1) {
console.log(1)
}
const c: string[] | null = [] as (string[] | null)
if (c?.length > 1) {
console.log(1)
}
CodePudding user response:
Consider the following:
const a: string[] | null = []
a // string[]
// TS has narrowed this to the array type in the union
// because you assigned an array to the variable after the type annotation
if (a?.length > 1) { // ok
console.log(1)
}
const b = [] as (string[] | null)
b // string[] | null
// You asserted that the array could be null,
// so it is now possibly null in the type system
if (b?.length > 1) {
// ~~~~~~~~~
// Object is possibly 'undefined'.(2532)
// TS won't allow comparison of a number to undefined
console.log(1)
}
const c: string[] | null = [] as (string[] | null)
c // string[] | null
// Another case of assertion
if (c?.length > 1) {
// ~~~~~~~~~
// Object is possibly 'undefined'.(2532)
// TS won't allow comparison of a number to undefined
console.log(1)
}
// I suggest one of these patterns instead:
declare const maybeNullMaybeArray: string[] | null;
if ((maybeNullMaybeArray?.length ?? 0) > 1) {
// ^^^^
// Use nullish coalescing operator to evaluate the expression to 0
// in the case that it is nullish, so that the comparison will
// always be between two numbers
maybeNullMaybeArray // string[] | null
// but the type is still not narrowed, because the true condition
// could have resulted from the evaluation to the literal zero
// instead of a value from the `length` property on the variable
// so, alternatively:
}
if (maybeNullMaybeArray && (maybeNullMaybeArray.length > 1)) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// The array definitely exists at this point
// so if we got this far, we can use it as an array:
maybeNullMaybeArray // string[]
}
See:
type assertions in the TS handbook
CodePudding user response:
Case A works because the compiler is smart enough to see that const a
is actually []
and not null
, although your explicit type declaration says something different.
In case b and c you're casting the value, telling the compiler that your variable might be null
. In that case b?.length
might resolve to undefined
. And undefined > 1
is not a valid comparison.
CodePudding user response:
In case A the assignment happens after the type has been declared, so typescript is able to figure out that a
is actually a string[]
. Therefore you don't even need the optional chaining operator there and can just use:
if (a.length > 1) {
console.log(1)
}
In the other cases, you first assign and then cast to a type, so typescript considers the type to be string[] | null
. The expression b?.length
returns undefined
for null values, so you end up with a comparison undefined > 1
which is invalid. To fix this, you should rewrite the condition as:
if (b && b.length > 1) {
console.log(1)
}
Here we make sure that b is not null before accessing the length
property and doing the comparison.
CodePudding user response:
The error is Object is possibly 'undefined'
. This feature is called "strict null checks
". In case A, the compiler can understand the value cant to be null, while in b and c the compile fails to determine and throws error Object is possibly 'undefined' as you try to get .length
method of undefined
You can use the non-null assertion operator!
to coerce away those types like b!?.length > 1
const a: string[] | null = []
if (a?.length > 1) {
console.log(1)
}
const b = [] as (string[] | null)
if (b!?.length > 1) {
console.log(1)
}
const c: string[] | null = [] as (string[] | null)
if (c!?.length > 1) {
console.log(1)
}