I will share with us some tips that improved my skill in Typescript ! Today we will learn satisfies
operator that appear in the 4.9 TS Update !
A "Boring" error
Let's go on the common error when using Record with multiple value.
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
const redComponent = palette.red.at(0); // Property 'at' does not exist on type 'string | RGB'.
Hm... It's really boring, we cannot direclty use array methods
since Typescript don't know if red
property is a string
or RGB
type ! It's because we use Record<Colors, string | RGB>
.
Typescript cannot narrow the correct type between string
and RGB
.
First solution: Narrow it
We can narrow the type !
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
function setColor(color: RGB | string) {
if (color instanceof Array) {
return color.forEach(); // We can use array methods
} else {
return color.toUpperCase(); // It's a string ! So we can use String methods
}
}
It's a solution but we need to add running code for typing things.
Second solution: No type
You can fix the issue with removing type.
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
const redComponent = palette.red.at(0);
It's also a "bad" solution, why ? You have no type, and you can add wrong key or misstypo thing !
const palette = {
red: [255, 0, 0],
green: null, // It should throw error
bleu: [0, 0, 255] // It should throw error since it's not 'bleu' but 'blue'
};
Third solution: as const
Brief introduction to as const
. as const
is used to block any editing on a variable, like a const assignement. This bloking allow typescript to narrow the correct type with more details.
const a: string = 'hello' // is string
const b = 'hello' as const // is 'hello'
Let's try !
const palette = {
"red": "yes",
"green": false,
"fakeKey": false, // It should throw error
"bleu": "kinda", It should throw error
} as const;
So it's like the old solution, there is no strict typing ! So it can leads to wrong key
or misstypo
!
Last solution: Create specific type from the basic type
In order to have a correct Narrowing, you can also create specific sub type
for each object.
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
type One = Record<"red", RGB> & Record<"green" | "blue", string>
const palette: One = {
red: [255, 0, 0],
green: "#00ff00",
blue: "#00ff00"
}
palette.red // RGB
palette.blue // string
It could be great since we have a correct narrowing ! But if you have a lot of key and value, I should say : Good luck
my friend.
So it's the time...
Satisfies, here we go
The new satisfies operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression.
TypeScript developers are often faced with a dilemma: we want to ensure that some expression matches some type, but also want to keep the most specific type of that expression for inference purposes.
As an example, we could use satisfies to validate that all the properties of palette are compatible with string | number[]
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>
palette.red // RGB !
palette.green // string !
It caught all possible error about typing,
const palette = {
red: [255, 0, 0],
green: "#00ff00",
wrongKey: "#00ff00", // Catch Error !
bleu: [0, 0, 255], // Catch Error !
} satisfies Record<Colors, string | RGB>
palette.green = [0, 0, 255] // ! green is basically string, it cannot be RGB !
We solve the problem !
If I should give a short definition about satifies
operator, it should be the following:
satisfies operator check a type like Record<string, A | B>, and narrow the correct type between A | B for each key
Bonus, satisfies + as const
You can add all benefits of as const
combinated with satifies
.
In this example:
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>
palette.red
is RGB
, not [255, 0, 0]
. It's normal since we can edit palette.red with another RGB value like [255, 255, 255]
!
If you need to have the strict value type
, you can use as const
.
type Colors = "red" | "green" | "blue";
// We use Readonly utils type for using as const
type RGB = Readonly<[red: number, green: number, blue: number]>
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} as const satisfies Record<Colors, string | RGB>
palette.red // [255, 0, 0]
palette.red = [255, 255, 0] // throw error since we cannot edit an property that is constant
I hope you like this reading!
☕️ You can SUPPORT MY WORKS 🙏
🏃♂️ You can follow me on 👇
🕊 Twitter : https://twitter.com/code__oz
👨💻 Github: https://github.com/Code-Oz
🇫🇷🥖 For french developper you can check my YoutubeChannel
And you can mark 🔖 this article!
Top comments (6)
This is a very satisfying upgrade to TS
Thanks !
Excelent, thank you! Very usefull 👍
thank you
witchcraft:-D
Very nice thanks!