Jonas Chapuis, Ph.D.
First launched in 2012
Spearheaded by Anders Hejlsberg, C# creator
Open source (Apache 2), backed by MS, on github
.d.ts
type definition file, for instance jquery.d.ts
let isDone: boolean = false;
isDone
false
let integer: number = 6;
let decimal: number = 6.555;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
undefined
integer
6
decimal
6.555
octal
484
let color: string = "blue";
color = "red";
let favorite = `Hello, my favorite color is ${color}.`
favorite
'Hello, my favorite color is red.'
let list: number[] = [1, 2, 3];
let genList: Array<number> = [1, 2, 3];
undefined
list
[ 1, 2, 3 ]
genList
[ 1, 2, 3 ]
let t: [string, number] = ["hello", 10];
t[0] = 1
[COMPILE ERROR] Line 2: Type '1' is not assignable to type 'string'.
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
c
1
Retrieve enum value from int:
let blue = Color[2];
blue
'Blue'
let notSureOfTheType: any = 4;
notSureOfTheType = "maybe a string instead";
notSureOfTheType.quack(); // compiler ok, maybe it's actually a duck
arrays with untyped elements:
let anyList: any[] = [1, true, "free"]; // we know it's an array, but not what's inside
anyList
[ 1, true, 'free' ]
function warnUser(): void {
alert("this is my warning message")
}
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;
--strictNullChecks
compiler flag¶function processValue(value: string): void {
// code here assumes param is non-null
}
processValue(null) // error TS2345: Argument of type 'null' is not assignable to parameter of type 'string'.
function processOptionalValue(value: string | undefined): void {
// code here deals with undefined values as well
}
processOptionalValue(undefined) // compiler ok
// Function returning never must have unreachable end point
function error(message: string): never {
throw new Error(message);
}
// Compile with --strictNullChecks
var a = []; // Type of a is never[]
a.push(5); // Error: Argument of type 'number' is not assignable to parameter of type 'never'
var b: number[] = [];
b.push(5); // compiler ok
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
strLength = (someValue as string).length;
16
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
}
return x;
}
undefined
f(true);
10
f(false);
undefined
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
undefined // just to avoid garbage in output
undefined
10 10 10 10 10 10 10 10 10 10
function fWithLet(shouldInitialize: boolean) {
if (shouldInitialize) {
let x = 10;
}
return x;
}
[COMPILE ERROR] Line 5: Cannot find name 'x'.
block scoping, one capture at each iteration
for (let i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
undefined // just to avoid garbage in output
undefined
0 1 2 3 4 5 6 7 8 9
const
¶const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat,
}
// Error
kitty = {
name: "Danielle",
numLives: numLivesForCat
};
// all "okay" – use readonly instead
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
[COMPILE ERROR] Line 8: Cannot assign to 'kitty' because it is a constant or a read-only property.
const input = [1, 2];
let [first, second] = input;
console.log(first, second);
[first, second] = [second, first]; // swap variables
console.log(first, second);
1 2 2 1
undefined
let t: [string, number] = ["hello", 10];
let [fst, scd] = t
console.log(fst, scd)
hello 10
undefined
let o = {
someA: "foo",
someB: 12,
someC: "bar"
};
o
{ someA: 'foo', someB: 12, someC: 'bar' }
let { someA, someB } = o;
undefined
someA
'foo'
someB
12
let { someA: newName1, someB: newName2 } = o;
undefined
newName1
'foo'
newName2
12
let x = [1, 2];
let y = [3, 4];
let bothPlusSome = [0, ...x, ...y, 5];
bothPlusSome
[ 0, 1, 2, 3, 4, 5 ]
let defaultOptions = { food: "spicy", price: "$$", ambiance: "noisy"};
let searchOptions = { ...defaultOptions, food: "rich"};
searchOptions
let someArray = [1, "string", false];
// iterate on values
for (let entry of someArray) {
console.log(entry);
}
1 string false
undefined
let someObject = {
foo: "bar",
bar: "foo"
}
// iterate on object keys
for (let key in someObject){
console.log(key);
}
foo bar
undefined
An object is deemed iterable if it has an implementation for the Symbol.iterator
property:
someArray[Symbol.iterator]
[Function: values]
you can always go functional with most of iterable types as well, with map
or forEach
someArray.map(v => v.toString());
[ '1', 'string', 'false' ]
function* idMaker(){
let index = 0;
while(index < 3)
yield index++;
}
let gen = idMaker();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
{ value: 0, done: false } { value: 1, done: false } { value: 2, done: false } { value: undefined, done: true }
undefined
Type-checking focuses on the shape that values have. This is called “duck typing” or “structural subtyping”, but done at compile time.
let obj = {size: 10, label: "Size 10 Object"};
// using plain JavaScript
function printLabelledJs(labelled) {
console.log(labelled.label);
}
printLabelledJs(obj);
Size 10 Object
undefined
let foo = {label : "foo"}
printLabelledTs(foo);
[COMPILE ERROR] Line 2: Cannot find name 'printLabelledTs'. Did you mean 'printLabelledJs'?
// using TypeScript interface definition
interface Labelled {
label: string;
}
function printLabelledItfc(labelled: Labelled) {
console.log(labelled.label);
}
undefined
interface Colored {
color : string;
}
interface Framed {
stroke: number;
}
interface Shape extends Colored, Framed {
width: number;
height: number;
}
// class declaration, implementing the combined interface
// - more about classes later
class Square implements Shape {
constructor(public width: number, public height: number, public color: string, public stroke: number){}
}
[Function: Square]
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
undefined
createSquare({color: "black"});
{ color: 'black', area: 100 }
interface Point {
readonly x: number;
readonly y: number;
}
undefined
let p1: Point = { x: 10, y: 20 };
p1.x = 5;
[COMPILE ERROR] Line 2: Cannot assign to 'x' because it is a constant or a read-only property.
Side note: read-only arrays
let ro: ReadonlyArray<number> = [1, 2, 3, 4];
ro[0] = 0;
[COMPILE ERROR] Line 2: Index signature in type 'ReadonlyArray<number>' only permits reading.
interface SearchFunc {
(source: string, part: string): boolean;
}
undefined
let mySearch: SearchFunc;
mySearch = function(source: string, part: string): boolean {
let result = source.search(part);
return result > -1;
}
mySearch("the quick brown fox jumps over the lazy dog", "jumps")
true
interface AgeMap {
[name: string]: number; // key type can only be string or number!
}
undefined
let ages: AgeMap = { "Jonas": 25, "Alfred": 50 };
ages["Jonas"];
25
// when targetting ES6, an alternative which allows for any key type is:
interface NameKey {
firstName: string,
lastName: string
}
let ageMap = new Map<NameKey, number>();
undefined
ageMap.set({firstName:"jonas", lastName: "chapuis"}, 25)
Map { { firstName: 'jonas', lastName: 'chapuis' } => 25 }
Moving away from prototypes towards traditional object-orientation
Animal
¶abstract class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
abstract makeSound(): void; // must be implemented in derived classes
}
[Function: Animal]
let animal = new Animal("Elephant");
[COMPILE ERROR] Line 1: Cannot create an instance of the abstract class 'Animal'.
Snake
¶class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
makeSound() {
$$.html("<audio src='snake.mp3' autoplay/>");
}
}
[Function: Snake]
Horse
: another derived class of Animal
¶class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
makeSound() {
$$.html("<audio src='horse.wav' autoplay/>");
}
}
[Function: Horse]
Let's play with our animals...
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
Slithering... Sammy the Python moved 5m. Galloping... Tommy the Palomino moved 34m.
undefined
sam.makeSound();
tom.makeSound();
class Vehicle {
// more compact version to declare class members (using modifier)
constructor(protected model:string, protected manufacturer:string, public description: string) {}
}
class Car extends Vehicle {
constructor(model: string, manufacturer : string, private tires:string) {
super(model, manufacturer, model + ", " + manufacturer + ", " + tires);
}
}
[Function: Car]
let car = new Car("S", "Tesla", "unknown");
car
Car { model: 'S', manufacturer: 'Tesla', description: 'S, Tesla, unknown', tires: 'unknown' }
car.manufacturer
[COMPILE ERROR] Line 1: Property 'manufacturer' is protected and only accessible within class 'Vehicle' and its subclasses.
car.tires
[COMPILE ERROR] Line 1: Property 'tires' is private and only accessible within class 'Car'.
class Immutable{
constructor(readonly data: string) {};
}
let immutable = new Immutable("some data");
immutable.data = "foobar"
[COMPILE ERROR] Line 5: Cannot assign to 'data' because it is a constant or a read-only property.
class Employee {
private _fullName: string;
get fullName(): string {
console.log("returning fullName...");
return this._fullName;
}
set fullName(newName: string) {
console.log("updating fullName...");
this._fullName = newName;
}
}
[Function: Employee]
let employee = new Employee();
employee.fullName = "Bob Smith";
employee.fullName
updating fullName... returning fullName...
'Bob Smith'
function add(x: number, y: number): number {
return x + y;
}
add(1, 1)
2
let myAdd = function(x: number, y: number): number { return x + y; };
myAdd(1, 1)
2
let myArrowAdd = (x: number, y: number) => { return x + y};
myArrowAdd(1, 1)
2
// default parameters
function check(text: string, timeout: number = 100): boolean {
return true;
}
// optional parameters
function validate(text: string, options?: string): boolean {
return true;
}
// variadic parameters
function searchContents(text: string, ...contents: string[]): string[] {
return [];
}
function serialize(o: number): string;
function serialize(o: string): string;
function serialize(o): string {
return JSON.stringify(o);
}
serialize(4);
'4'
serialize('text');
'"text"'
serialize(true)
[COMPILE ERROR] Line 1: Argument of type 'true' is not assignable to parameter of type 'string'.
class AlertsFactory {
private alertsCount: number = 0;
createAlerterFor(problem: string) {
return () => {
console.log("MAXIMUM ALERT: "+problem);
this.alertsCount++;
console.log("total number of alerts issued: " + this.alertsCount);
}
}
}
[Function: AlertsFactory]
let factory = new AlertsFactory();
let alerter1 = factory.createAlerterFor("crash");
let alerter2 = factory.createAlerterFor("bug");
alerter1();
alerter2();
alerter1();
MAXIMUM ALERT: crash total number of alerts issued: 1 MAXIMUM ALERT: bug total number of alerts issued: 2 MAXIMUM ALERT: crash total number of alerts issued: 3
undefined
function printArray<T>(array: Array<T>) {
array.forEach(el => console.log(el));
console.log("total elements count: "+array.length);
}
undefined
printArray(["foo", "bar"])
foo bar total elements count: 2
undefined
interface Entity {
id(): string;
}
interface Repository<T extends Entity> {
getEntityWithId(id: string): T;
}
class DummyEntity implements Entity {
constructor(private _id: string, public payload: string){}
id(): string {
return this._id;
}
}
[Function: DummyEntity]
class DummiesRepository implements Repository<DummyEntity> {
private entitiesPerId = new Map<string, DummyEntity>()
constructor() {
this.entitiesPerId["foo"] = new DummyEntity("foo", "some foo payload");
this.entitiesPerId["bar"] = new DummyEntity("bar", "some bar payload");
}
getEntityWithId(id: string): DummyEntity {
return this.entitiesPerId[id];
}
}
let dummies = new DummiesRepository();
dummies.getEntityWithId("foo");
DummyEntity { _id: 'foo', payload: 'some foo payload' }
Pattern matching with TypeScript!
interface Branch {
kind: "branch"; // string literal type
left: Tree;
right: Tree;
}
interface Leaf {
kind: "leaf";
value: number;
}
type Tree = Branch | Leaf
function sumLeaves(tree: Tree): number {
switch(tree.kind){
case "branch": return sumLeaves(tree.left) + sumLeaves(tree.right);
case "leaf": return tree.value;
}
}
undefined
sumLeaves(
{
kind: "branch",
left: {
kind: "branch",
left: { kind: "leaf", value: 1},
right: { kind: "leaf", value: 1}
},
right: { kind:"leaf", value: 1}
}
)
3
Dynamic lookup, but with compile-type safety!
function getProperty<T, K extends keyof T>(o: T, key: K): T[K] {
return o[key]; // o[key] is of type T[K]
}
undefined
interface Person {
name: string;
age: number;
}
undefined
type PersonKeys = keyof Person // "name" | "age" (union type of string literals)
type PersonLookupType = Person[PersonKeys] // string | number (union type of Person's properties type)
function getPersonProperty<Person, PersonLookupType>(o: Person, key: PersonKeys): PersonLookupType {
return o[key]; // returns the property, with type-safety on key and proper return type
}
undefined
let person: Person = {
name: 'Jonas',
age: 25
};
undefined
getProperty(person, 'name');
'Jonas'
getProperty(person, 'age');
25
getProperty(person, 'unknown');
[COMPILE ERROR] Line 1: Argument of type '"unknown"' is not assignable to parameter of type '"name" | "age"'.
TypeScript's "type-level" or "aspect-oriented" programming
Symbols are a way to declare global identifiers for "aspects" that objects can implement - code can then rely on these aspects being present. Example of pre-built symbols:
Symbol.iterator // used by for..of, returns the default iterator for an object
Symbol(Symbol.iterator)
Symbol.hasInstance // used by instanceof operator, determines if an object is of one class
Symbol(Symbol.hasInstance)
It's just about providing the [Symbol.iterator]
property:
class IdMaker {
[Symbol.iterator] = function*() {
var index = 0;
while(true && index<3) {
yield index++;
}
}
}
var maker = new IdMaker();
for (let id of maker) { // note: when targetting ES5, use the --downlevelIteration compiler flag
console.log(id);
}
0 1 2
undefined
TypeScript official documentation is pretty good and mostly up-to-date (!): many code examples and the structure of this presentation follow the TypeScript Handbook
For more detailed and up-to-date information, directly on the github repo, the 2000+ issues in particular