umma.dev

TypeScript: Part Two

A deep dive into TypeScript interfaces and how types are linked.

What Are Interfaces?

Interfaces are an abstract type that enable you to define properties within a given object. Looking at the properity names and data type for each one.

When Are Interfaces Used?

In a React application, you might use interfaces to define the type of props used within the application.

Are Types and Interfaces Linked?

Yes, you must define the type of data within the interface.

Examples

You can use eithe type or interface when building interfaces. See below.

interface Person {
  name: string;
  age: number;
}

function greeting(person: Person) {
  return "Hello " + person.name + " you are " + person.age + " years old.";
}

Is the same as…

type Person = {
  name: string;
  age: number;
};

function greeting(person: Person) {
  return "Hello " + person.name + " you are " + person.age + " years old.";
}

Optional Properties

Optional properties use a ? to signify they are optional.

interface CoOrds {
  xPos?: number;
  yPos?: number;
}

function getCorOrds(dest: CoOrds) {
  return CoOrds.xPos + "," + CoOrds.xPos;
}

getCoOrds({ xPos: 100, yPos: 100 });
getCoOrds({ xPos: 100 });
getCoOrds({ yPos: 100 });

Readonly Properties

Behaviour won’t change at runtime however if a property is marked as readonly it can’t be written to during type checking.

interface Example {
  readonly text: string;
}

function readOnlyExample(obj: Example) {
  return obj.text;
}

Note: text cannot be re-assigned in the function

Using readonly doesn’t always imply the value is immutable, it just means the property itself cannot be rewritten to.

interface Person {
  name: string;
  age: number;
}

interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}

let writablePerson: Person = {
  name: "Person A",
  age: 100,
};

let readonlyPerson: ReadonlyPerson = writeablePerson;

console.log(readonlyPerson.age); // 100
writeablePerson.age++;
console.log(readonlyPerson.age); // 101

Using mapping modifiers, you can remove readonly attributes.

Array Type

function Example(value: Array<string>) {
  return console.log("example");
}

let myArr: string[] = ["one", "two"];

Example(myArr);
Example(new Array("one", "two"));

Array itself is also a generic type.

interface Array<Type> {
  length: number;
  pop(): Type | undefined;
  push(...items: Type[]): number;
}

Modern JavaScript also provides other data structures which are generic, like Map<K, V>, Set<T>, and Promise<T>. All this really means is that because of how Map, Set, and Promise behave, they can work with any sets of types.

Readonly Array Type

This type describes arrays that shouldn’t be changed.

const myArr: ReadonlyArray<string> = ["one", "two", "three"];

You can’t do the following:

let x: readonly string[] = [];
let y: string[] = [];

x = y;
x = x;

Unlike the readonly property modifier, assignability isn’t bidirectional between Arrays and ReadonlyArrays.

Tuple Types

Another sort of Array type that knows exactly how many elements it contains and exactly which types it contains at specific positions.

type StrNumPair = [string, number];
function Example(pair: [string, number]) {
  const a = pair[0];
  const b = pair[1];
}

Example(["hello", 100]);
function Example(stringHash: [string, number]) {
  const [inputStr, hash] = stringHash;
  console.log(inputStr);
  console.log(hash);
}
interface StringNumber {
  length: 2;
  0: string;
  1: number;

  slice(start?: number, end?: number): Array<string | number>;
}

readonly Tuple Types

function Example(pair: readonly [string, number]) {
  return pair;
}

Index Signatures

Sometimes you don’t know all the names of a type’s properties ahead of time. In those cases you can use an index signature to describe the types of the possible values.

interface StringArr {
  [index: number]: string;
}

const myArr: StringArr = getStringArr();
const secondItem = myArr[1];

The following code block wouldn’t work:

interface Example {
  [index: string]: number;
  length: number;
  name: string; // name cannot be a string as index only has type number
}

However the following would work:

interface ExampleFixed {
  [index: string]: number | string;
  length: number;
  name: string;
}

You can make index signatures readonly in order to prevent assigment to indices.

interface ReadonlyStringArray {
  readonly [index: number]: string;
}

let myArr: ReadonlyStringArray = getReadOnlyStringArr();
myArr[2] = "Example";

Extending Types

The extends keyword on interface allows you to copy members from other named types and add new properties. This can be useful for cutting down declaration boilerplate.

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postCode: string;
}

interface AddressWithUnit {
  name?: string;
  unit: string;
  street: string;
  city: string;
  country: string;
  postCode: string;
}

interface AddressWithUnit extends BasicAddress {
  unit: string;
}
interface SelectColour {
  colour: string;
}

interface Circle {
  radius: number;
}

interface SelectColour extends Circle {}

const cc: ColourfulCircle = {
  clour: "green",
  radius: 3.14,
};

Intersection Types

Interfaces allow you to build new types and extend types from other types. TypeScript provides intersection types and is mainly used to combine existing object types.

Intersection type is defined by &.

interface SelectColour {
  color: string;
}

interface Circle {
  radius: number;
}

type SelectColour = SelectColour & Circle;