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.