This article is meant for people who know the basics of TypeScript but haven’t used it in a large project, and/or haven’t explored its advanced types.
My goal is to convince you, based on a ton of examples, that TypeScript’s powerful advanced type system can provide satisfying solutions for complex web engineering problems and can be used for mind-blowing and fun modeling.
Watch this video and refer to code snippets below.
Building Blocks
These are types you can find on the official TypeScript documentation. They are the building blocks for more advanced/fancy stuff. Think of this section as a cheat-sheet.
Intersection types
Get all members from multiple types:
If you think of types as sets, what you get here is the intersection of two types, which is a smaller set. This can be confusing as the actual resulting type has a “larger” interface, i.e. the union of the members of both types.
Union types
Get common members from multiple types (the resulting type is the union between the two, i.e. a larger set):
Custom Type Guards
Conditional type cast:
With the is keyword you can define functions to determine “by hand” whether a value belongs to a type, and TypeScript then assumes that type in the true part of a conditional statement.
Type Assertion
A cast shorthand if you know that a specific variable is definitely defined:
Literal Types
You can use actual primitive literals (strings, booleans etc.) as types:
If you think of types as sets, the type 'ROCK' is the set containing only the value 'ROCK'.
Never
An expression that never resolves:
This type will magically appear in many surprising spots in the examples below.
Unknown
A safer type than any for variables that you can’t trust:
Index Types
Access the types of object keys or values:
Mapped Types
Derive an object type from another:
This powerful abstraction will make more sense when you see specific applications of it.
Conditional Types
Let a type depend on another type with a condition:
Here’s an actual application for conditional types:
With the infer keyword you can introduce a type variable, the type of which will be inferred:
Misc Patterns
Functional Programming
This is not exactly related to advanced types, rather it’s an illustration of how one can program in a lightweight but type safe way with TypeScript. In Java world, in the Clean Code paradigm there are two main categories of classes: objects and data types. Data types represent data (think getters and setters), while objects represent functionality (think methods). With TypeScript one can simplify this approach even more by just using interfaces to represent data and functions to manipulate data. No bulky boilerplate, no constructors, getters and setters needed. Just return a plain object and typescript will ensure it is correctly structured.
Discriminate Unions
TypeScript understands you have exhaustively checked all possible variants of a type in a switch statement, and won’t force you to use a default statement.
Derive Types From Constants
Often we need literal types both as values and as types. To avoid redundancy derive the latter from the former.
Untrusted User Input
Untrusted user input is an application for unknown.
Mapped Types
Create a readonly version of a type:
Derive a partial version of a type:
Derive a version of a type where all fields are required:
Derive a nullable version of a type:
Derive a type with only specific fields (Pick):
An example from practice, where we select specific columns from a SQL table, and TypeScript automatically derives the partial type for us:
Derive a record (all properties have same given type) from a type:
Conditional Types
Filter union types
Filter / extract a subset from a type:
Diff / exclude a subset from a type:
Exclude null and undefined from a typed (thus making it non-nullable):
Omit specific fields from a type:
Here is an example from practice (how to type a React.js HOC correctly):
Combining with mapped types
Get the function properties of an object:
Get the non-function properties of an object:
Get the return type of a function:
Get the instance type of a constructor function:
Promisifying Redis
Another example from practice. All methods in the npm redis library use callbacks. We like to use promises and async/await. We can derive the new type from the old based on how we build it.
Conclusion
I hope to have convinced you of the power of TypeScript. Now go ahead and TYPE!