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.
In TypeScript, it's common to feel frustration that you can't use dot notation to access type properties.
ts
typePerson = {name : string;age : number;};typeCannot access 'Person.name' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'name' in 'Person' with 'Person["name"]'?2713Cannot access 'Person.name' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'name' in 'Person' with 'Person["name"]'?Name =Person .name ;
But there are several good reasons the TypeScript team hasn't implemented it.
ts
typePerson = {name : string;age : number;};typeName =Person ["name"];typeAllValues =Person ["name" | "age"];
You're used to two different syntaxes in JavaScript for accessing property values:
ts
constperson = {name : "Ada",age : 42,};// Dotsconstname =person .name ;// Square bracketsconstage =person ["age"];
So why does TypeScript only allow the second syntax?
ts
typePerson = {name : string;age : number;};typeCannot access 'Person.name' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'name' in 'Person' with 'Person["name"]'?2713Cannot access 'Person.name' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'name' in 'Person' with 'Person["name"]'?PersonName =Person .name ;typePersonAge =Person ["age"];
The error discovered above is usefully phrased.
'Person' is a type, but not a namespace.
TypeScript namespaces are a way of grouping together types and values into a single spot.
And unlike types, they allow you to use the dot notation OR square bracket notation to access their members.
ts
namespaceMyMath {export constPI = 3.14;export typeVector = {x : number;y : number;};}constpi =MyMath ["PI"];typeVector =MyMath .Vector ;
Namespaces are, in general, out of favor. They were brought in as a potential solution to 'modules' in JavaScript before ES Modules came along - so they're a legacy feature.
Namespaces also compile to runtime code. The code above will end up looking like this:
var MyMath;
(function (MyMath) {
MyMath.PI = 3.14;
})(MyMath || (MyMath = {}));
const pi = MyMath["PI"];
So - namespaces are really just objects, and objects can be accessed with dot notation or square bracket notation.
All this to say - we now understand the error we were getting before. But why is it happening in the first place?
Let's take another look at our property access syntax:
ts
typePerson = {name : string;age : number;};typeName =Person ["name"];
It's important to remember that this wasn't always available in TypeScript.
Specifically, it's called Indexed Access Types, and it landed in TypeScript 2.1 in 2016.
When the TypeScript team ships a new feature, they tend towards minimum syntactical impact. In other words - the fewest possible syntaxes for doing the same thing.
So, when they added the ability to access properties on types, they only chose one syntax - square bracket notation.
Square bracket notation is far more flexible than dot notation. For example, it lets you accept unions:
ts
typePerson = {name : string;age : number;};typeValues =Person ["name" | "age"];
Or even another type - perhaps extracted from the target itself. A popular pattern is to pass keyof
to an indexed access type:
ts
// Simple Object values!typeValues2 =Person [keyofPerson ];
A version of this with dot notation would be hard to imagine:
type Person = {
name: string;
age: number;
};
// Bleugh
type Values = Person.(keyof Person);
So - the TypeScript team chose the most flexible syntax and left it at that.
The TypeScript team is very careful about adding new syntax to the language, and they're unlikely to add new syntax for a feature that's already possible.
Remco Haszing on Twitter also pointed out that bringing in this syntax would conflict with the namespace syntax:
TypeScript lets you declare an interface with the same name as a namespace - meaning the access notation acts as a differentiator between the type and the namespace.
ts
namespaceMyMath {export typePI = 3.14;}interfaceMyMath {PI : number;}// Resolves to the typetypePi =MyMath ["PI"];// Resolves to the namespacetypePi2 =MyMath .PI ;
Gross - but this is the kind of thing that the TypeScript team has to consider when adding new syntax.
So, it's highly unlikely. We'll be stuck with our square bracket notation for a while yet.
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.