
TypeScript 4 Design Patterns and Best Practices
By :

Understanding the basic language constructs of TypeScript is very valuable when learning design patterns. You will need to recognize valid TypeScript code and some of its features because it will help you define better typings for objects, as well as help you avoid mistakes. We will strive to provide small but consistent examples and use cases of TypeScript idioms and constructs for completeness.
The basic structure of a TypeScript program consists of statements or expressions. The following is a list of basic types that are partly associated with JavaScript runtime types:
number
, string
, Boolean
, void
, null
, undefined
, unknown
, never
, unique
, bigint
, and any
values. To define or declare them, you need to write the name of the variable, followed by a semicolon (:) and its type. If you assign the wrong type, then the compiler will throw an error. Here is an example usage of those types (intro.ts
):const one: string = "one"; const two: boolean = false; const three: number = 3; const four: null = null; const five: unknown = 5; const six: any = 6; const seven: unique symbol = Symbol("seven"); let eight: never; // note that const eight: never cannot happen as we cannot instantiate a never
enum Keys { Up, Down, Left, Right, } let up: Keys = Keys.Up;
You can enforce a compiler optimization with enums to make them constant, thus eliminating any unused information:
const enum Bool { True, False, } let truth: Bool = Bool.True;
const arr: number[] = [1, 2, 3]; // array of numbers of any size
Tuples represent a fixed array, with each element having a defined type:
const tup: [number] = [1]; // tuple with one element of type number
class User { private name: string; constructor(name: string) { this.name = name; } public getName(): string { return this.name; } } const user = new User("Theo"); console.log(user.getName()); // prints "Theo"
You can also define abstract classes (that is, regular classes) that cannot be instantiated. Instead, they need to be inherited as part of a parent-child relationship:
abstract class BaseApiClient { abstract fetch(req: any): Promise<any>; /* must be implemented in sub-classes*/ } class UsersClient extends BaseApiClient { fetch(req: any): Promise<any> { return Promise.resolve([]); } } const client = new UsersClient(); client.fetch({url: '/users'});
Comparable
interface like this:interface Comparable<T> { compareTo(o: T): number }
Note that we are not defining an implementation for compareTo
here, just its type. Interfaces in TypeScript can also have properties:
interface AppConfig { paths: { base: string; }; maxRetryCount?: number; }
The question mark (?
) after the name represents an optional parameter, so it's allowed to create a type with or without it:
const appConfig: AppConfig = { paths: { base: '/', } }
Type is a similar concept to interfaces but is a bit more flexible. You can combine a Type
with another Type
either as a union
or as an intersection
type:
type A = 'A'; // type is 'A' type B = 'B'; // type is 'B' type C = A & B; /* type is never as there is nothing in common between A and C*/ type D = C | "E"; // type is "E" as C is a never type type E = { name: string; } type F = E & { age: number; } let e: F = { name: "Theo", age: 20 }
Note
As a rule of thumb, you should be declaring interfaces first. However, when you want to combine or create new types on the fly, then you should use types.
There are many other notable features of TypeScript that you will learn about throughout this book. Now, let's move on and learn how to handle input and output.
Understanding how to read from input and write to output is one of the most fundamental skills of any programming language. Handling input and output operations with TypeScript depends primarily on where you use it. For example, when using TypeScript in a browser environment, you accept input from user interactions, such as when a user clicks on a button and submits a form or when you send an AJAX request to a server.
When using TypeScript in a server, you can read input values from command-line arguments or from the standard input stream (stdin
). Subsequently, we can write values to the output stream, called the standard output stream (stdout
). All these concepts are common to all computer environments.
As an example, let's take a case where we are using TypeScript with Node.js. We can use the following simple program to read from stdin
and write to stdout
:
inputOutput.ts
const stream = process.stdin; setImmediate(function () { stream.push(null); }); stream.pipe(process.stdout);
Then, you can invoke this program from the command line:
echo "Hello" | npm run ts chapters/chapter-1_Getting_Started_With_Typescript_4/inputOutput.ts Hello World
Working with streams exposes a different programming model, called reactive programming, where you are concerned about asynchronous data streams and events. You will learn more about asynchronous communication patterns in Chapter 7, Reactive Programming with TypeScript.
The latest version of TypeScript (v4.2) offers a great list of features that help developers write type-safe programs and abstractions. For example, with TypeScript 4, we have the following:
type Point2d = [number, number]; type Point3d = [number, number, number]; const point1: Point2d = [1, 2]; const point2: Point3d = [1, 2, 3];
Before TypeScript 4, you could not pass a variadic type parameter for a tuple as the shape of the tuple had to be defined. Now, let's check out the following code:
type NamedType<T extends unknown[]> = [string, ...T]; type NamedPoint2d = NamedType<Point2d>; const point3: NamedPoint2d = ["Point: (1, 2)", 1, 2];
Here, the type of NamedPoint2d
is [string
, number
, number
]. With this feature, we may have more compelling reasons to use tuples to model domain primitives.
type Point2dL = [x: number, y: number]; type Point3dL = [x: number, y: number, z: number];
Labeled tuples are useful for documentation purposes; so, if you use tuples, you should also provide labels for them.
type Suit = `${"Spade" | "Heart" | "Diamond" | "Club"}`; type Rank = `${"2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "Jack" | "Queen" | "King" | "Ace"}` type Deck = `${Rank} of ${Suit}`;
If you inspect the type
of the Deck
declaration, you will see that it enumerates the possible cards of a standard 53 deck of cards: 2 of Spade, 3 of Spade …, Ace of Club.
Now that we've introduced and understood TypeScript 4's features, let's learn how TypeScript and JavaScript are related to each other.
Change the font size
Change margin width
Change background colour