Reputation: 30047
Using const assertion, one can nicely narrow an object/array literal's type to its elements.
e.g.
const arr = [
[5, "hello"],
[5, "bye"],
] as const;
type T = typeof arr; // type T = readonly [readonly [5, "hello"], readonly [5, "bye"]]
(Without as const
, T
would be type T = (string | number)[][]
, which is very wide and sometimes unwanted.)
Now, the problem is that as a result of that as const
the array becomes readonly
as well, while I just it to have a narrowed type. So, it cannot be passed to the following function.
function fiveLover(pairs: [5, string][]): void {
pairs.forEach((p) => console.log(p[1]));
}
fiveLover(arr); // Error
And the Error is:
Argument of type
'readonly [readonly [5, "hello"], readonly [5, "bye"]]'
is not assignable to parameter of type'[5, string][]'
. The type'readonly [readonly [5, "hello"], readonly [5, "bye"]]'
is'readonly'
and cannot be assigned to the mutable type'[5, string][]'
.(2345)
How can I narrow the type, without getting the unwanted readonly
attribute? (Preferably at the object/array creation time.)
Upvotes: 0
Views: 1132
Reputation: 33091
Typescript as
operator looks a bit hacky for me. I'm trying to avoid it as much as possible.
Please, try next example:
const arr = [
[5, "hello"],
[5, "bye"],
] as const;
function fiveLover<T extends ReadonlyArray<readonly [5, string]>>(pairs: T): void {
pairs.forEach((p) => console.log(p[1]));
}
fiveLover(arr); // No Error
Btw, I'm always trying to operate on immutable values. IF you are not mutating your value, there is no sence to remove readonly
flag
UPDATE
It is possible to infer without const assertion
but you need to provide literal object instead of reference. There is no other way
function fiveLover<
Fst extends number,
Scd extends string,
Tuple extends [Fst, Scd],
Tuples extends Tuple[]
>(pairs: [...Tuples]): void {
pairs.forEach((p) => console.log(p[1]));
}
fiveLover([
[5, "hello"],
[5, "bye"],
]); // ok
Upvotes: 1
Reputation: 30047
In order to get rid of the readonly
property and make the object or array mutable, we need the following utility function:
type DeepWritable<T> = { -readonly [P in keyof T]: DeepWritable<T[P]> };
(Source: https://stackoverflow.com/a/43001581)
Now we can do one of the two things:
That is:
fiveLover(arr as DeepWritable<typeof arr>);
Which is great, and keeps the object intact. But then, if we are using the arr
array in several places for similar mutable usage, we need to do it every single time.
Which is what I needed in my real project, since I am using that arr
object in a lot of places, and it being readonly causes trouble.
The solution would be defining a utility function, which is just an identity function in run-time, but DeepWritable
in compile-time, as following:
function writableIdentity<T>(x: T): DeepWritable<T> {
return x as DeepWritable<T>;
}
const arr2 = writableIdentity([
[5, "hello"],
[5, "bye"],
] as const);
fiveLover(arr2); // Works fine!
This way, the object is only manipulated/converted once, and it can be used as an ordinary, type-narrowed, non-readonly variable anywhere!
Upvotes: 0