Issue
Imagine an array of items which can be a string, or a function that returns a string:
const arr: (string | (() => string))[] = [/* ... */]
TypeScript is happy with the following ternary expression testing the type of an array item and then returning the item or the result of calling the item:
// This works
const value = typeof arr[0] === 'string' ? arr[0] : arr[0]()
However, when indexing the array using a variable inside a loop, TypeScript complains that "Not all constituents of type 'string | (() => string)' are callable."
for (let i = 0; i < arr.length; i++) {
// This fails: "not all constituents are callable"
const value = typeof arr[i] === 'string' ? arr[i] : arr[i]()
}
But assigning the array item to a variable first succeeds:
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
// This works
const value = typeof item === 'string' ? item : item()
}
My question is why this happens. I suspect it's because the variable could change between testing the type and calling the function. But even when assigned as const
, which prevents reassignment, it still fails in the same way:
for (let i = 0; i < arr.length; i++) {
const j = i
// This still fails
const value = typeof arr[j] === 'string' ? arr[j] : arr[j]()
}
How does TypeScript process this?
Solution
The compiler does not narrow a type if it's not a simple expression. Let's take this as the example:
const value = typeof arr[i] === 'string' ? arr[i] : arr[i]()
arr[i]
- This might seem simple, but actually, i
is only known at runtime. That means the compiler doesn't know what i
is; all it knows is that i
is a number. Compare this to:
arr[0]
- This is known at compile-time. The key 0
is constant (as it is hard coded) and the compiler can infer that the key 0
of this object is narrowed here:
const value = typeof arr[0] === 'string' ? arr[0] : arr[0]()
// ~~~~~~
And actually, if you narrow i
to a literal like 0
, you can then use it as if it were 0
:
if (i === 0) {
const value = typeof arr[i] === 'string' ? arr[i] : arr[i](); // OK
}
That's also why if you put arr[i]
in a variable before using it, you can now narrow it...
const thing = arr[i];
thing
- It's become a simple expression that can't be reduced any further. The compiler knows that this value is a string
or () => string
, and when you narrow it, it's able to determine that it's now a string
:
const value = typeof thing === "string" ? thing : thing();
// ~~~~~
In other words, individual variables can be narrowed, but not more complex expressions that rely on other variables. That's simplifying it a bit but that is how it works, in essence.
Answered By - caTS
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.