A deep dive into TypeScript interfaces and how types are linked.
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.
In a React application, you might use interfaces to define the type of props used within the application.
Yes, you must define the type of data within the interface.
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 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 });
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.
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.
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.
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>;
}
function Example(pair: readonly [string, number]) {
return pair;
}
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";
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,
};
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;