Announcing: A Free Book, A New Course, A Huge Price Cut...
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
There’s a difference between using TypeScript and knowing TypeScript.
The docs give you a good grasp of the pieces like generic functions, conditional types, and type helpers.
But out in the wild, developers are combining these pieces together into patterns.
Four of the most important patterns to know and use are:
Let’s dive in!
Here’s some example code that uses a branded type:
type Password = Brand<string, ‘Password’>;
const takesInPassword = (password: Password) => {}
// Will error! takesInPassword(‘4123123’)
// Won’t error! takesInPassword(‘4123123’ as Password)
Branded types let you assign different ‘labels’ to values. In the code above, the Password
type can only be satisfied by something that has been branded as Password
.
This creates validation boundaries in your app’s code - you can validate that something is a valid password, then safely pass it around your application.
The second is globals.
Understanding how the ‘global type scope’ works in TypeScript is critical. Love them or hate them, globals are a part of the way we use JavaScript. And if we use them in JavaScript, we need to be able to describe them in TypeScript.
For instance: declare global
lets you type functions and variables directly in the global scope:
declare global { function myFunc(): boolean; var myVar: number; }
console.log(myVar); console.log(myFunc());
You can use similar techniques to add types to process.env
, type Window
, and even create your own global types.
The third pattern is assertion functions and type predicates.
Type predicates let you specify that a function returns a boolean which describes one of the arguments passed to it:
const values = [“a”, “b”, undefined, “c”, undefined];
const filteredValues = values.filter((value): value is string => Boolean(value), );
// filteredValues is now string[]
And assertion functions work similarly, but let you throw an error inside a function to ‘assert’ that the value passed corresponds to a certain type.
function assertUserIsAdmin(
user: NormalUser | AdminUser,
): asserts user is AdminUser {
if (user.role !== "admin") {
throw new Error("Not an admin user");
}
}
Both of these patterns let you improve TypeScript’s ability to narrow your code.
The fourth pattern is ****classes****.
Classes?!
Yes.
The humble ES6 class, in TypeScript’s hands, becomes an amazing tool for enacting the builder pattern. This design is at the core of tRPC, one of TypeScript’s most popular libraries.
Combined with assertion functions, you can even use it to type the shape of the class from inside the class itself.
export class SDK {
loggedInUser?: User;
constructor(loggedInUser?: User) {
this.loggedInUser = loggedInUser;
}
assertIsLoggedIn(): asserts this is this & { loggedInUser: User } {
if (!this.loggedInUser) {
throw new Error("Not logged in");
}
}
}
Classes are often misaligned and misunderstood.
“I thought we used hooks now??”
Look, when you’re writing TS in library-land, classes are a force of pragmatic utility that you can’t sleep on.
Having a shared set of core patterns is an absolute super power for any team, and that’s what the Advanced Patterns Workshop will do for you. This Advanced Patterns workshop is available now as part of the Total TypeScript Core Volume!
Share this article with your friends
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.
Learn how to use corepack
to configure package managers in Node.js projects, ensuring you always use the correct one.
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.