Skip to main content

TypeScript: Types

Intro​

This post will cover the different types available in TypeScript. TypeScript has a rich type system that allows developers to define types for variables, functions, and more.

General Syntax​

In TypeScript, types are defined using the : character followed by the type name. For example:

let x: number = 10;

So we can see that we are simply adding a type to our variable with variable: type in the normal course of our definition.

Primitive Types​

JavaScript has three very commonly used primitives: string, number, and boolean. Each has a corresponding type in TypeScript.

πŸ“˜ typescriptlang.org > everyday-types

boolean​

// Define a boolean type variable and initialize it
let isActive: boolean = true;

// You can also declare a boolean variable without initialization
let isFinished: boolean;

// Later assign a boolean value
isFinished = false;

console.log(isActive); // Output: true
console.log(isFinished); // Output: false

number​

// Define a number type variable and initialize it
let age: number = 30;

// Another number variable for a floating-point value
let price: number = 19.99;

// You can also declare a number variable without initialization
let temperature: number;

// Later assign a number value
temperature = 25.5;

console.log(age); // Output: 30
console.log(price); // Output: 19.99
console.log(temperature); // Output: 25.5

another type of number that is available in TypeScript is bigint. BigInt is a new primitive type in JavaScript that allows you to work with arbitrarily large integers. BigInt values are created by appending an n to the end of an integer literal or by using the BigInt function.

// Define a bigint type variable and initialize it with a literal
let largeNumber: bigint = 1234567890123456789012345678901234567890n;

// Another bigint can be created using the BigInt function
let anotherLargeNumber: bigint = BigInt(
"99999999999999999999999999999999999999"
);

// Performing arithmetic operations with bigint
let sum: bigint = largeNumber + anotherLargeNumber;

console.log(largeNumber); // Output: 1234567890123456789012345678901234567890n
console.log(anotherLargeNumber); // Output: 99999999999999999999999999999999999999n
console.log(sum); // Output of summing both large numbers
note

If you are wondering when in the world you could ever possibly run into a situation where you will need to handle bigints, I have run into them before with an API that was using them as ID's. That was not fun.

string​

// Define a string type variable and initialize it
let name: string = "Alice";

// Another string variable for a greeting message
let greeting: string = "Hello, " + name + "!";

// You can also declare a string variable without initialization
let address: string;

// Later assign a string value
address = "123 Maple Street, Anytown, USA";

console.log(name); // Output: Alice
console.log(greeting); // Output: Hello, Alice!
console.log(address); // Output: 123 Maple Street, Anytown, USA

Falsy Types​

We also have some "falsy" types in TypeScript. These are types that are considered false when evaluated in a boolean context. The falsy types in TypeScript are void, null, undefined, 0, NaN, false, and the empty string "".

void​

In TypeScript, the void type is used to indicate that a function does not return a value. It's commonly used for functions that perform an action but do not return a result. Here's an example to demonstrate how to use the void type in TypeScript:

function logMessage(message: string): void {
console.log(message);
}

logMessage("Hello, TypeScript!"); // Outputs: Hello, TypeScript!

If a function is defined to return void, you can implicitly return null or undefined, or have no return statement at all. The TypeScript compiler treats null and undefined as valid return values for functions declared with a return type of void. This is because void in TypeScript is about indicating that the return value of the function is not important and should not be used.

Here’s how this looks in practice:

function returnNothing(): void {
console.log("This function returns nothing explicitly.");
return; // implicitly returns undefined
}

function returnUndefined(): void {
console.log("This function returns undefined.");
return undefined; // explicitly returns undefined
}

function returnNull(): void {
console.log("This function returns null.");
return null; // explicitly returns null
}

returnNothing();
returnUndefined();
returnNull();

In all these cases, the TypeScript compiler will not complain about return undefined or return null in a function declared as returning void. However, stylistically, it is more typical to see functions returning void without any return statement or just a plain return; if needed to exit early from the function. The primary takeaway is that when a function is declared to return void, any returned value should not be relied upon in your code.

undefined​

I find undefined to actually be very interesting here. Here is a basic example declaring a variable to be undefined.

let myVariable: undefined;

console.log(myVariable); // Output will be 'undefined'

In the context of TypeScript and type systems in general:

  • Declaring a variable involves specifying a name and, optionally in TypeScript, a type. The declaration tells the compiler that a variable exists with a given identifier and type.

  • Defining a variable often refers to the process of allocation and, in many cases, initialization. However, in TypeScript and other statically-typed languages, defining a variable can also simply mean declaring it with a specific type, as this sets the constraints on the variable's behavior within the code.

  • Initializing a variable involves assigning it an initial value at the point of definition. Initialization is not required at the time of declaration in TypeScript, especially when you declare a variable with a type like undefined or even other types. You can declare a variable first and then initialize it later in the code.

So note that we have declared a variable to be undefined but we have not initialized it. This is perfectly valid in TypeScript.

null​

Null is also interesting.

let item: null = null;

console.log(item); // Output will be 'null'

So why were we able to declare an undefined variable without initializing it, but with null we had to initialize it?

In TypeScript, you cannot declare a variable with the type null without initializing it. The reason for this is that null is a subtype of all other types except for undefined. This means that a variable explicitly typed as null can only ever hold the value null, making it practically immutable. Therefore, TypeScript requires that if you declare a variable as type null, you must also initialize it to null at the point of declaration.

If you try to declare a variable with the type null without initializing it, like this:

let item: null;

TypeScript will throw a compilation error saying:

Variable 'item' is used before being assigned.

It's more common in TypeScript to use null as part of a union type, but we will get to that later.

Object Types​

In TypeScript, object types are a way to define the shape of an object. They specify the structure that an object must follow, including which properties the object should have, along with their types.

Interface​

Interfaces in TypeScript are used to type-check whether an object fits a certain structure. By defining an interface, you can name a specific combination of variables, ensuring any object that implements the interface will have exactly the same structure, with types in place.

interface User {
id: number;
name: string;
age?: number; // The question mark makes this property optional
}

function registerUser(newUser: User) {
console.log(`User ${newUser.name} registered with ID ${newUser.id}.`);
}

// Creating a user object that adheres to the User interface
let user: User = {
id: 1,
name: "Alice",
age: 25, // This field is optional
};

registerUser(user); // Function call with a user object

Class​

A class is a blueprint for creating objects with specific properties and methods. It encapsulates data for the object. TypeScript enhances classes with features like types and visibility modifiers which are not present in JavaScript.

Here’s a breakdown of a simple TypeScript class:

  1. Class Declaration: You begin with the class keyword followed by the class name.
  2. Properties: Defined attributes with types that every class instance will have.
  3. Constructor: A special method for assigning values to the properties of the class when creating an instance.
  4. Methods: Functions that provide behavior for the class.
class Person {
// Properties
name: string;
age: number;

// Constructor
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

// Method
describe(): string {
return `${this.name} is ${this.age} years old.`;
}
}

// Creating an instance of Person
const person1 = new Person("Alice", 30);
console.log(person1.describe()); // Outputs: Alice is 30 years old.
tip

Key differences between Interface and Class

  1. Class: A class is a concrete blueprint for creating objects. It can include properties, methods, and a constructor. Classes support inheritance, meaning you can create a derived class that extends the functionality of a base class. Classes are also part of the JavaScript output, as they are translated into JavaScript functions.

  2. Interface: An interface is purely a TypeScript construct used to type-check the shape that objects have. It cannot have any concrete implementation. Interfaces define properties, methods, and event signatures which must be adhered to by any object that implements the interface. Interfaces are used for declaring the expected structure but disappear in the JavaScript output, as JavaScript does not have a concept of interfaces.

Here’s the key difference:

  1. Implementation: Classes provide implementation details. Interfaces do not; they only define the shape and the expected properties and methods.
  2. Presence in JavaScript: Classes exist in both TypeScript and JavaScript, while interfaces are used only in TypeScript for static type checking and do not exist after the TypeScript code is compiled to JavaScript.

Enum​

An enum is a way to organize a collection of related values. Enums allow you to define a set of named constants, making your code more readable and manageable. Using enums can help to reduce the number of magic numbers or strings in your code and group values with a similar purpose under a single umbrella.

Types of Enums​

Numeric Enums: By default, enums are numeric. TypeScript automatically assigns each member a number, starting from zero. String Enums: You can also define enums using string values for more readable and meaningful outputs, which is particularly useful in debugging.

Numeric Enum Example​

Here's an example of a numeric enum to represent days of the week:

enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}

// Accessing an enum value
console.log(Day.Monday); // Outputs: 1
console.log(Day[1]); // Outputs: Monday

String Enum Example​

String enums are more descriptive, as each member is initialized with a string value rather than a numeric one.

enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}

// Using string enum
console.log(Direction.Left); // Outputs: LEFT

String enums provide the advantage of retaining the full information about the enum members in the runtime JavaScript, which can be helpful for debugging and for interfacing with other systems that expect specific string values.

Enums add a layer of clarity to your code by allowing you to use predefined constants, which can make your code easier to write, read, and maintain.

tip

I like to think of an enum as being similar to a validation list in excel. You can only choose from the values in the list.

Array​

Arrays can be declared in two ways.

// Using square brackets
let numbers: number[] = [1, 2, 3, 4, 5];
console.log(numbers[0]); // Outputs: 1

// Using the Array generic type
let moreNumbers: Array<number> = [6, 7, 8, 9, 10];
console.log(moreNumbers[0]); // Outputs: 6

Tuple​

A tuple is a type that allows you to express an array with a fixed number of elements whose types are known, but need not be the same. Tuples are useful when you want to store a combination of values of different types together within a single array structure.

Characteristics of Tuples

  • Fixed Length: Unlike regular arrays, tuples have a fixed number of elements.
  • Ordered: The type of each element is known (and needs to be specified) at a specific position in the tuple.
  • Type-Safe: Each position in the tuple can have a different type, and operations on tuple elements are type-checked by the compiler.
// Define a tuple type
let book: [string, number, boolean];

// Initialize correctly
book = ["1984", 1949, true]; // Correct

// Trying to initialize incorrectly - TypeScript will throw an error
// book = [1949, "1984", true]; // Error: Type 'number' is not assignable to type 'string'

console.log(book[0]); // Outputs: 1984
console.log(book[1]); // Outputs: 1949

RegExp​

In TypeScript, RegExp is not a primitive type, but it is a built-in object type that represents regular expressions. You can use RegExp to create a regular expression object for matching text with a pattern.

let myRegex: RegExp = new RegExp("abc");

// Alternatively, you can use the literal notation:
let myLiteralRegex: RegExp = /abc/;

// Example of using the regex to test a string
const result: boolean = myRegex.test("abcdef"); // true
console.log(result);

Other Types​

any​

The any type is a powerful way to bypass the language's strict type-checking system. When you use the any type for a variable, you can assign any value to that variable without TypeScript performing type checks. This means that you can work with variables without knowing their type beforehand, which can be useful in dynamic content or when interfacing with libraries and existing JavaScript code where the type information is not available.

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

// The variable `notSure` can be assigned and re-assigned to any type of value without TypeScript complaining.
console.log(notSure); // Outputs: false

// You can also call any method or access any properties, TypeScript will not check:
notSure.ifItExists(); // No compile-time error, but this will result in "TypeError: notSure.ifItExists is not a function" at runtime if not defined.

notSure.toFixed(); // This will compile but will fail at runtime if `notSure` is not a number at the time of execution.
tip

Using any should be a temporary solution or limited to scenarios where type safety cannot be achieved due to the dynamic nature of the content. Ideally, use more specific types like unknown or type assertions as soon as practical.

unknown​

The unknown type represents any value but is safer than any because it doesn't allow you to perform arbitrary operations on values of type unknown without an explicit type assertion. This means you must first confirm the type of an unknown value before performing operations specific to that type.

// Assume we get this data from a JSON API response
let response: unknown = "Hello, world!";

// Direct operations on 'unknown' types aren't allowed without type checking
// console.log(response.length); // Error: Object is of type 'unknown'.

// Type checking
if (typeof response === "string") {
console.log(response.length); // Outputs: 13
}

// Another example using a function and type assertion
function safelyHandle(data: unknown): void {
if (typeof data === "string") {
console.log(data.toUpperCase());
} else {
console.log("Not a string");
}
}

safelyHandle(response); // Outputs: HELLO, WORLD!
tip

unkown is particularly useful when handling data from API's.

object​

The object type is used to represent any non-primitive type.

Using the object type allows you to specify that a variable should be able to hold any object value. However, without more specific information about what shape or properties the object should have, you can't directly access any properties of an object safely, because TypeScript doesn't know what structure it might have.

function logObjectKeys(obj: object): void {
// Direct property access on 'obj' would be an error:
// console.log(obj.name); // Error: Property 'name' does not exist on type 'object'.

// To manipulate or access properties, you need to use type assertions
const specificObj = obj as { name: string; age: number };
console.log("Name:", specificObj.name);
console.log("Age:", specificObj.age);
}

const person = { name: "Alice", age: 30 };
logObjectKeys(person);

never​

The never type represents the type of values that never occur. It is used in scenarios where a function never returns a value or where a variable is in a state that cannot exist. This can happen in two main cases: when a function throws an error, preventing it from returning normally, or when a function has an infinite loop, effectively never reaching its end.

Some characteristics of the never type:

  • Control Flow Analysis: TypeScript uses the never type to analyze code for errors and control flow paths.
  • No Values: No value can be of type never, and no never variable can be assigned a value without causing a type error.
  • Function Return Types: It's particularly useful as a return type for functions that always throw an error or never terminate.
function error(message: string): never {
throw new Error(message);
}

function infiniteLoop(): never {
while (true) {}
}

// Using a never function
try {
error("Something went wrong!");
} catch (e) {
console.log(e.message); // Outputs: Something went wrong!
}

// Note: Calling infiniteLoop() here would block the script indefinitely

Common usage:

  • Error Handling Functions: Functions designed to handle errors by throwing exceptions or logging severe errors that should stop execution.
  • Assertions: In custom type assertion functions that throw an error if the type does not match an expected type.
  • Infinite Loops: Though rare in application-level code, useful in certain backend or system-level processes where a service is intended to run indefinitely.

Conclusion​

This post covered the different types available in TypeScript, including primitive types, falsy types, object types, and other types like any, unknown, object, and never. Understanding the different types in TypeScript is crucial for writing type-safe code and leveraging the full power of the language's type system.

In the next post, we will cover type assertions in TypeScript, which allow you to inform the TypeScript compiler about the type of a variable or expression when the type is known to you but cannot be automatically inferred by the compiler.

Automated Amazon Reports

Automatically download Amazon Seller and Advertising reports to a private database. View beautiful, on demand, exportable performance reports.

bidbear.io