Issue
This is my code, I expected bike({name:"bike"})
, but it's throwing an error. I don't know why it's behaving this way.
const crateBox = <BikeData, CarData>()=>{
const bike = (data:BikeData)=>{
console.log("bike data: ", data);
}
const car = (data:CarData)=> {
console.log("car data: ", data);
}
return [bike, car];
}
const [bike, car] = crateBox<{name:string}, {type:string}>();
bike({name:"bike"})
The Playground:
But if I change const [bike, car] = createBox<{name:string}, {type:string}>();
to const {bike, car} = createBox<{name:string}, {type:string}>();
, it works correctly. However, I want to use the format [bike, car]
and not encounter an error.
How to solve this problem?
const crateBox = <BikeData, CarData>()=>{
const bike = (data:BikeData)=>{
console.log("bike data: ", data);
}
const car = (data:CarData)=> {
console.log("car data: ", data);
}
return {bike, car};
}
const {bike, car} = crateBox<{name:string}, {type:string}>();
bike({name:"bike"})
Solution
Because you didn't annotate the return type of crateBox()
, TypeScript infers the type from the value you returned, which was the array literal [bike, car]
. Type inference relies on heuristic rules to choose the most appropriate type. For array literals, the compiler assumes that the developer wants a plain array type, which represents an unordered list of arbitrary length. People often modify arrays by adding elements to them, removing elements from them, or reordering them. So this is a reasonable assumption.
But that means [bike, car]
is inferred as type (((data: BikeData) => void) | ((data: CarData) => void))[]
. So all the compiler keeps track of is that each element of that array is either a BikeData
consumer or a CarData
consumer. It doesn't know the locations of them in the array or how long the array is.
That doesn't meet your needs. The assumption that you wanted an unordered arbitrary-length array type was incorrect.
What you apparently want is for the return type of crateBox()
to be a tuple type. A tuple type is an array whose length and types of elements at each position are known and fixed. In your case, it would look like the type [(data: BikeData) => void, (data: CarData) => void]
, an array of length 2, whose first element is a BikeData
consumer, and whose send element is a CarData
consumer.
Now, you could just annotate the return type and give up entirely on inference:
const crateBox =
<BikeData, CarData>(): [(data: BikeData) => void, (data: CarData) => void] => {
// ------------> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const bike = (data: BikeData) => { console.log("bike data: ", data); }
const car = (data: CarData) => { console.log("car data: ", data); }
return [bike, car]; // okay
}
const [bike, car] = crateBox<{ name: string }, { type: string }>();
bike({ name: "bike" }); // okay
That works inside the function because now the compiler knows the exact type you wanted, and it can verify that [bike, car]
matches that type. And it works outside the function because now the type of bike
is the first element of the tuple type, and thus (data: { name: string }) => void
.
But you don't need to go that far. Instead you can use a const
assertion on the [bike, car]
expression you're returning. This still uses type inference, but it tells the compiler that you really want it to pay attention to the exact literal value; array literals should be given tuple types, and other primitive literals should be given literal types. If you do that:
const crateBox = <BikeData, CarData>() => {
const bike = (data: BikeData) => { console.log("bike data: ", data); }
const car = (data: CarData) => { console.log("car data: ", data); }
return [bike, car] as const;
}
Now the return type of crateBox
is readonly [(data: BikeData) => void, (data: CarData) => void]
. This is a readonly
tuple type which isn't known to have mutating methods like push()
or sort()
, and won't let you reassign the values. But otherwise it's the same as the annotated tuple type [(data: BikeData) => void, (data: CarData) => void]
from the previous section.
And that means the compiler will know that the first element of the returned array is a BikeData
consumer, and the second is a CarData
consumer, and thus you get the behavior you want with minimal changes to your code:
const [bike, car] = crateBox<{ name: string }, { type: string }>();
bike({ name: "bike" }); // okay
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.