How To Strongly Type process.env
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Last week, the TypeScript team released TS 5.3.
As usual, I scanned the announcement post, but I quickly noticed something interesting.
One of the most important changes in TypeScript 5.3 wasn't mentioned in the release notes.
ts
// This would error in 5.2, but is allowed in 5.3!constarray = ["a", "b", "c"] asconst satisfies string[];constreturnWhatIPassIn = <constT extends any[]>(t :T ) => {returnt ;};// result is any[] in TS 5.2, but ['a', 'b', 'c'] in 5.3constresult =returnWhatIPassIn (["a", "b", "c"]);
Working with readonly arrays in TS can be occasionally a bit of a pain.
Let's say you want to declare an array of routes as const.
This allows you to reuse the paths declared for a type.
ts
constarrayOfRoutes = [{path : "/home",component :Home },{path : "/about",component :About },] asconst ;typeRoute = (typeofarrayOfRoutes )[number]["path"];
But what if you want to make sure that the arrayOfRoutes
conforms to a certain type?
For that, you can use satisfies.
ts
constarrayOfRoutes = [{path : "/home",component :Home },{path : "/about",component :About },] asconst satisfies {path : string;component :React .FC ;}[];// Type is 'readonly' and cannot be// assigned to a mutable type
The only trouble is that in TypeScript 5.2, this would give an error! But... Why?
Well, it's because arrayOfRoutes
is readonly, and you can't satisfy a mutable array with a readonly one.
So, the fix was to make the type we were satisfying a readonly
array:
ts
constarrayOfRoutes = [{path : "/home",component :Home },{path : "/about",component :About },] asconst satisfies readonly {path : string;component :React .FC ;}[];// No more error!
Now that we're using a readonly array, TypeScript is happy.
The same was true when using const type parameters, but even more pernicious.
In this position, const
infers the thing passed into T
as if it were as const
.
But if you try to constrain it with an array, it doesn't work!
ts
constreturnWhatIPassIn = <constT extends any[]>(t :T ) => {returnt ;};// result is any[] in TS 5.2!constresult =returnWhatIPassIn (["a", "b", "c"]);
Before TS 5.3, the fix was to add readonly
to the type parameter:
ts
constreturnWhatIPassIn = <constT extends readonly any[]>(t :T ) => {returnt ;};// result is ['a', 'b', 'c']!constresult =returnWhatIPassIn (["a", "b", "c"]);
But this fix was hard to find and required some deep knowledge of how const type parameters worked.
Since 5.3, TypeScript has relaxed the rules around readonly arrays.
In these two situations, TypeScript now acts more helpfully.
The satisfies
keyword now lets you pass in readonly arrays:
ts
// This would error in 5.2, but is allowed in 5.3!constarray = ["a", "b", "c"] asconst satisfies string[];
Const type parameters now infer the type passed in instead of defaulting to their constraints:
ts
constreturnWhatIPassIn = <constT extends any[]>(t :T ) => {returnt ;};// result is any[] in TS 5.2, but ['a', 'b', 'c'] in 5.3constresult =returnWhatIPassIn (["a", "b", "c"]);
Note that there is a small difference! If you were to specify readonly string[]
instead of string[]
, you would get a readonly array back.
So you still need to specify readonly if you want a readonly array back.
ts
// This would error in 5.2, but is allowed in 5.3!constarray = ["a","b","c",] asconst satisfies readonly string[];constreturnWhatIPassIn = <constT extends readonly any[]>(t :T ) => {returnt ;};// result is any[] in TS 5.2, but ['a', 'b', 'c'] in 5.3constresult =returnWhatIPassIn (["a", "b", "c"]);
But this is a massive improvement, making both const
type parameters and satisfies
much easier to work with.
TypeScript - you gotta shout about these things! I'll be updating my course with this new behavior very soon.
Share this article with your friends
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Discover when it's appropriate to use TypeScript's any
type despite its risks. Learn about legitimate cases where any
is necessary.
Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.
Improve React TypeScript performance by replacing type & with interface extends. Boost IDE and tsc speed significantly.
In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.
Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.