💡 TypeScript, unlike Java, understands types as shapes.
If a class quacks and walks like a duck and an interface quacks and walks like a duck then they are interchangeable from TypeScript's point of view (at least as a definition).
class Animal {
name: string; // plain public uninitialized
private readonly id: number = 0; // readonly, default value
constructor (name: string) {
this.name = name;
}
// we got public (default), private, protected
protected rename (newName: string) {
this.name = newName;
}
}
class Dog extends Animal {
private _bites: boolean = false; // unfortunately underscore IS needed
private isAngry: boolean = false;
static saySomething (): void { // static method
console.log('something');
}
bark (words: string = 'woof woof') {
console.log(`${this.name} barks "${words}"`);
}
get bites (): boolean {
return this._bites;
}
// without this setter _bites would be a readonly
set bites (b: boolean) {
this._bites = b;
if (b) {
this.isAngry = true;
}
}
}
let fido = new Dog('Fido');
Abstracts can not be instanciated! They serve as a base template for other classes.
abstract class AlphaMale {
abstract bite (): void;
}
Mixins can be emulated through C implements A, B
(where A and B are classes), but the effective mixin logic must be implemented manually!
This means that one must copy (mix) the prototype methods from A and B to C using an applyMixins
implementation.
Interfaces are just interface definitions and as such they will be removed during the TS compilation process.
One can use interfaces to describe the shape of
interface Book {
readonly title: string;
readonly subtitle?: string; // <-- notice the question mark, this is an optional prop
readonly author: string;
read?: boolean;
score?: number;
}
let oldBook: Book = { title: 'Candide', subtitle: 'All for the best', author: 'Voltaire' };
let newBook: Book = { title: 'Men Without Women', author: 'Murakami Haruki' };
oldBook.read = true;
newBook.read = false;
interface FuzzyDog {
name: string;
owner?: string;
[propName: string]: any; // <-- this can be anything (both the key and the value!)
}
let janesDog: FuzzyDog = { name: 'Fido', owner: 'Jane', favoriteFood: 'tripe ration' };
let strayDog: FuzzyDog = { name: 'Socks', age: null, marked: true };
interface FindFn {
(needle: string, haystack: string[]): boolean;
}
let searchForOwner: FindFn = (s, names) => names.includes(s.toLowerCase());
interface VeryComplicatedReadonlyArray {
readonly [index: number]: string;
}
let boyNames: VeryComplicatedReadonlyArray = [ 'Joe', 'Jack', 'John' ];
let girlNames: ReadonlyArray<string> = [ 'Jane', 'Jill', 'Judy' ];
interface Dog {
type: string;
bark (count: number): void;
}
class GuardDog implements Dog {
type: 'german shepherd';
bark (count: number) {
for (let i = 0; i < count; i++) {
console.log('Woof!');
}
}
}
Multiple interfaces may be extended at once.
interface Shape {
color: string;
}
interface Circle {
radius: number;
}
interface Disc extends Shape, Circle {
fillColor: string;
}
TS can merge interfaces with the same name (unlike Classes):
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
const box: Box = {height: 1, width: 1, scale: 3}