
Chapter Outline
TypeScript Fundamentals: Understanding Types and Interfaces
TypeScript adds powerful features to JavaScript, enabling developers to write more reliable and maintainable code. One of the core features of TypeScript is its type system, which helps catch errors at compile time. In this article, we'll explore TypeScript's types and interfaces, understand their usage, and see how they can improve your development workflow. We'll also include code examples and tests using Jest to illustrate their practical application.
Basic Types
TypeScript provides several basic types that you can use to annotate your variables and function parameters.
Boolean
This basic data type expresses a simple true/false value.
typescriptlet isDone: boolean = true;
Number
In JavaScript, as well as TypeScript, all numbers are either floating point values or BigIntegers. The floating point numbers are designated the type number, while BigIntegers are designated the type bigint. In addition to decimal and hexadecimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.
typescript1let decimal: number = 11;2let hex: number = 0xf00d;3let binary: number = 0b1011;4let octal: number = 0o744;5let bigNumber: bigint = 100n;
String
Text data in JavaScript is generally represented as data of type string. This is also the case in TypeScript.
typescript1let color: string = "blue";2color = 'red';3let sentence: string = `Hello ${name}!`;
Array
JavaScript allows an array to be an ordered collection of values, of any type. However TypeScript allows enforcing the type of data that is contained within an array. Here are the ways an array may be declared in TypeScript:
typescript1let list: number[] = [0, 1, 3];2let evens: Array<number> = [2, 4, 6];
Tuple
Tuples are arrays with a fixed number of elements whose types are known. You can use tuples to express values such as the positions x, y, z in Cartesian coordinates, or longitude, latitude values representing geographic data.
typescript1let x: [string, number];2key = ["key", 1001]; // OK3key = [10001, "key"]; // Error
Enum
Many high level languages such as C# and Java has had enumerated data types since their inception. JavaScript does not. TypeScript adds this feature which allows a more friendly way to assign names to a set of numeric values.
typescript1enum Color {2 Red,3 Green,4 Blue,5}67let c: Color = Color.Green;
It is also possible to manually set the values in the enum:
typescript1enum Position {2 First = 1,3 Second = 2,4 Third = 3,5}67let myPosition: Position = Position.Second;
The names of the enum values can be accessed by indexing into the enum as shown below:
typescript1enum Color {2 Red,3 Green,4 Blue,5}67let name: string = Color[2];8// Displays 'Green'9console.log(name);
Unknown
At times we may need to declare a variable whose type isn't known at compile time. For instance the value may come from dynamic content such as value returned from an API. In this instance, we want to instruct the compiler as well as future readers of the code that this variable could be anything, so we give it the unknown type.
typescript1let notSure: unknown = 4;2notSure = "maybe a string instead";34// OK, definitely a boolean5notSure = false;
unknown types cannot be directly assigned to another variable without performing a type check, such as a type guard:
typescript1declare const maybe: unknown;2// 'maybe' could be a string, object, boolean, undefined, or other types3const aNumber: number = maybe;4// Type 'unknown' is not assignable to type 'number'.56if (maybe === true) {7 // TypeScript knows that maybe is a boolean now8 const aBoolean: boolean = maybe;9 // So, it cannot be a string10 const aString: string = maybe;11 //Type 'boolean' is not assignable to type 'string'.12}
Any
Sometimes you may need to opt-out of type checking all together, either when the type data is not available, or would take an inordinate amount of time to specify. In these scenarios, we label the variables with any type.
typescript1declare function getValue(key: string): any;2// OK, return value of 'getValue' is not checked3const str: string = getValue("myString");
The any type is a powerful way to work with existing JavaScript source code, and gradually opt-into the TypeScript typing system.
Unlike unknown, variables of type any can access arbitrary properties, without any errors. This is similar to how JavaScript has been handling object properties.
All properties of an any type object are also of type any:
typescript1let looselyTyped: any = {};2let d = looselyTyped.a.b.c.d;3// `d` is of type `any`
Null and Undefined
On their own, undefined and null are not terribly useful. However, null and undefined are subtypes of all other types. Therefore, you can assign null or undefined to a variable of any other type, such as a string.
However, when using the strictNullChecks flag in tsconfig.json, applications of null and undefined are limited. This helps avoid many common errors.
Never
The never type represents the type of values that never occur. As an example, never is the return type of a function that always throws an error, or never returns.
typescript1// Function returning never must not have a reachable end point2function error(message: string): never {3 throw new Error(message);4}56// Inferred return type is never7function fail() {8 return error("Something failed");9}1011// Function returning never must not have a reachable end point12function infiniteLoop(): never {13 while (true) {}14}
Object
object is a type that represents composite types, i.e. anything that are not number, string, boolean, bigint, symbol, null, or undefined.
typescript1declare function create(o: object | null): void;2// OK3create({ prop: 0 });4// Argument of type 'number' is not assignable to parameter of type 'object'5create(42)
About Number, String, Boolean, Symbol and Object
The types Number, String, Boolean, Symbol and Object are not equivalent to their lowercase versions. These types do not refer to the language primitives, and should almost never be used as types.
typescript1function reverse(s: String): String {2 return s.split("").reverse().join("");3}45reverse("hello world");
Type Annotations
You can explicitly specify the types of variables and function parameters to make your code more predictable and readable.
typescript1function greet(name: string): string {2 return `Hello, ${name}!`;3}45let greeting: string = greet('Alice');
Type assertions
Type assertions are a way to tell the compiler "trust me". A type assertion is like a type cast in other languages, but it performs no special checking or restructuring of data. It has no runtime impact, and is simply used by the compiler.
Assertions have two forms.
as-syntax:
typescript1let someValue: unknown = "Hello World!";2let strLen: number = (someValue as string).length;
"angle-bracket" syntax:
typescript1let someValue: unknown = "Hello World!";2let strLen: number = (<string>someValue).length;
When using TypeScript with JSX, only
as-style assertions are allowed.
Interfaces
Interfaces are used to define the shape of objects, making your code more expressive and enforcing a contract for your objects.
typescript1interface User {2 id: number;3 name: string;4 email: string;5}67function getUser(): User {8 return {9 id: 1,10 name: 'John Doe',11 email: 'john.doe@example.com'12 };13}1415let user: User = getUser();
Type Aliases
Type aliases are similar to interfaces, but they can also represent primitive types, union types, and more.
typescript1type ID = number;2type Status = 'active' | 'inactive';34interface Product {5 id: ID;6 name: string;7 status: Status;8}910function getProduct(): Product {11 return {12 id: 101,13 name: 'Laptop',14 status: 'active'15 };16}1718let product: Product = getProduct();
Combining Types and Interfaces
You can combine types and interfaces to create more complex types.
typescript1type Address = {2 street: string;3 city: string;4 zipcode: string;5};67interface Customer {8 id: ID;9 name: string;10 address: Address;11}1213function getCustomer(): Customer {14 return {15 id: 202,16 name: 'Jane Doe',17 address: {18 street: '123 Main St',19 city: 'Somewhere',20 zipcode: '12345'21 }22 };23}2425let customer: Customer = getCustomer();
Type Guards
TypeScript type guards are functions or expressions that allow you to narrow down the type of a variable within a conditional block. They help the TypeScript compiler understand the specific type of a variable, enabling more precise type checking and preventing runtime errors.
Here are some common types of type guards and examples to illustrate their usage:
typeof Type Guards
The typeof operator can be used to narrow down types to primitive types such as string, number, boolean, and symbol.
typescript1function printValue(value: string | number): void {2 if (typeof value === "string") {3 console.log(`String value: ${value}`);4 } else if (typeof value === "number") {5 console.log(`Number value: ${value}`);6 }7}89printValue("Hello, World!"); // String value: Hello, World!10printValue(42); // Number value: 42
instanceof Type Guards
The instanceof operator can be used to narrow down types to specific classes.
typescript1class Dog {2 bark() {3 console.log("Woof!");4 }5}67class Cat {8 meow() {9 console.log("Meow!");10 }11}1213function makeSound(animal: Dog | Cat): void {14 if (animal instanceof Dog) {15 animal.bark();16 } else if (animal instanceof Cat) {17 animal.meow();18 }19}2021const dog = new Dog();22const cat = new Cat();2324makeSound(dog); // Woof!25makeSound(cat); // Meow!
User-Defined Type Guards
User-defined type guards use a function to check whether a value is of a specific type. These functions return a boolean value and have a specific return type called a type predicate.
typescript1interface Bird {2 fly(): void;3}45interface Fish {6 swim(): void;7}89function isBird(animal: Bird | Fish): animal is Bird {10 return (animal as Bird).fly !== undefined;11}1213function makeAnimalMove(animal: Bird | Fish): void {14 if (isBird(animal)) {15 animal.fly();16 } else {17 animal.swim();18 }19}2021const bird: Bird = {22 fly: () => console.log("Flying")23};2425const fish: Fish = {26 swim: () => console.log("Swimming")27};2829makeAnimalMove(bird); // Flying30makeAnimalMove(fish); // Swimming
in Operator Type Guards
The in operator can be used to check whether an object has a specific property.
typescript1interface Car {2 drive(): void;3}45interface Boat {6 sail(): void;7}89function moveVehicle(vehicle: Car | Boat): void {10 if ("drive" in vehicle) {11 vehicle.drive();12 } else if ("sail" in vehicle) {13 vehicle.sail();14 }15}1617const car: Car = {18 drive: () => console.log("Driving")19};2021const boat: Boat = {22 sail: () => console.log("Sailing")23};2425moveVehicle(car); // Driving26moveVehicle(boat); // Sailing
Combining Type Guards
You can combine different type guards to handle complex type narrowing scenarios.
typescript1interface Admin {2 admin: true;3 manage: () => void;4}56interface User {7 admin: false;8 view: () => void;9}1011function isAdmin(person: Admin | User): person is Admin {12 return person.admin === true;13}1415function isUser(person: Admin | User): person is User {16 return person.admin === false;17}1819function handlePerson(person: Admin | User): void {20 if (isAdmin(person)) {21 person.manage();22 } else if (isUser(person)) {23 person.view();24 }25}2627const admin: Admin = {28 admin: true,29 manage: () => console.log("Managing")30};3132const user: User = {33 admin: false,34 view: () => console.log("Viewing")35};3637handlePerson(admin); // Managing38handlePerson(user); // Viewing
Conclusion
Understanding types and interfaces is fundamental to mastering TypeScript. By defining explicit types and using interfaces to describe the shape of your objects, you can write more reliable and maintainable code. These practices also help catch errors early in the development process, improving your overall development workflow.
If you're new to TypeScript, I encourage you to start by annotating your variables and functions with types and gradually move on to defining interfaces for your objects. As you become more comfortable with TypeScript's type system, you'll find that it significantly enhances your productivity and code quality.
For further reading and in-depth tutorials, check out these resources:
Happy coding!