JulianGaramendy.dev
Readonly<T> and Better Error Messages
5 February, 2020A few weeks ago I learned something about TypeScript errors and utility types.
The following is true in TypeScript v3.7.5. In my experience, error messages in TS improve a lot with each release, so this may soon be irrelevant.
As usual, here's an example with bananas.
I had this Banana
type:
type Banana = {
id: number;
name: string;
color: number;
weight?: number;
length?: number;
thumbnail?: string;
pictures?: Array<string>;
};
I was trying to be clever and I thought I would make my type immutable by using the Readonly utility type like this:
type Banana = Readonly<{
id: number;
name: string;
color: number;
weight?: number;
length?: number;
thumbnails?: string;
pictures?: ReadonlyArray<string>;
}>;
But this turned out not to be such a great idea.
I had made a (Readonly)Map with a few bananas:
const bananaMap: ReadonlyMap<number, Banana> = new Map([
[1, { id: 1, name: "yellow banana", color: 0xffff00 }],
[2, { id: 2, name: "red banana", color: 0xff0000 }],
[3, { id: 3, name: "green banana", color: 0x00ff00 }]
]);
Then I tried to access an element in this way:
const banana: Banana = bananaMap.get(1);
And I got an error on the banana
identifier which looked like this:
Type 'Readonly<{ id: number; name: string; color: number; weight?: number
| undefined; length?: number | undefined; thumbnails?: string | undefined;
pictures?: readonly string[] | undefined; }> | undefined' is not
assignable to type 'Readonly<{ id: number; name: string; color: number;
weight?: number | undefined; length?: number | undefined; thumbnails?:
string | undefined; pictures?: readonly string[] | undefined; }>'.
Type 'undefined' is not assignable to type 'Readonly<{ id: number;
name: string; color: number; weight?: number | undefined; length?:
number | undefined; thumbnails?: string | undefined; pictures?:
readonly string[] | undefined; }>'.
It took me a while to understand what the problem was. The important part was on the second "paragraph" and I had to scroll down to find it, and still stare at it for a while.
You can see this example in the TypeScript Playground
So I changed the type, marking each field as readonly
instead:
type Banana = {
readonly id: number;
readonly name: string;
readonly color: number;
readonly weight?: number;
readonly length?: number;
readonly thumbnails?: string;
readonly pictures?: ReadonlyArray<string>;
};
Then the error message looked a bit better:
Type 'Banana | undefined' is not assignable to type 'Banana'.
Type 'undefined' is not assignable to type 'Banana'.
And the issue was evident! The get
method in the Map
class returns an element or undefined
. That means we can't annotate const banana
with the Banana
type.
const banana: Banana = bananaMap.get(1); // ❌ error!
Some possible fixes:
// 1
const banana: Banana | undefined = bananaMap.get(1); // ✅ no error
// 2
const banana: Banana = bananaMap.get(1)!; // ⚠️ no error, but (*)
* I would avoid the non-null assertion operator when possible.
You can see this new example in the TypeScript Playground.
UPDATE:
My friend Albert pointed our that we can get the "nice and short" error message mentioning out Banana
type if we use interface
instead of type
:
interface Banana extends Readonly<{
id: number;
name: string;
color: number;
weight?: number;
length?: number;
thumbnails?: string;
pictures?: ReadonlyArray<string>;
}> { }
You can see this last example in TypeScript Playground
I would love to hear what you use to declare immutability in your code.
Please comment!
Comment on dev.to: https://dev.to/juliang/readonly-t-and-better-error-messages-3i9l