So, finally doing a UI in reactJs that reached a point where the lack of typing in the silly language annoyed me too much.

Time for Typescript, which, it turns own is pretty full featured, pretty simple. BUT, almost too much so, and loads of experienced typescript devs start to reduce their use of language features back to vanilla javascript. A recent find is the link below, which isn’t a 100% rule set, but is a really good read.

https://medium.com/@martin_hotell/10-typescript-pro-tips-patterns-with-or-without-react-5799488d6680

With react Hooks

https://levelup.gitconnected.com/usetypescript-a-complete-guide-to-react-hooks-and-typescript-db1858d1fb9c

Here goes:

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md

https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html

Really good read to cut through the huge mess that is npm dev envs: https://www.freecodecamp.org/news/how-to-set-up-a-typescript-project-67b427114884/

Running ts scripts using https://www.npmjs.com/package/ts-node

Rest is dead, https://relay.dev/

https://www.typescriptlang.org/docs/handbook/advanced-types.html

And a shout out to Pluralsite who kindly gave a free 1 month access during lockdown.

Getting started

https://create-react-app.dev/docs/adding-typescript/

Or, for another perspective: https://levelup.gitconnected.com/setting-up-a-full-stack-typescript-application-featuring-express-and-react-ccfe07f2ea47

cd all_dev
mkdir typescript
cd typescript
npx create-react-app typescript-examples --template typescript
cd typescript-examples

and pause while the entire world is downloaded…

Then open up VisualCode, File->open folder File->Save workspace as

At this point I accept that any node app should run a webserver - either the built in node server or express. There is little point trying to replicate other languages with their main() or extends App etc.

Key files are the VisualCode launcher spec in .vscode/launch.json and the typescript config file tsconfig.json, tslint.json. You can google them yourself.

VisualCode?

In my next post I create a client and server project, and it was during this that I chucked out VisualCode and purchased IntelliJ pro edition. It is just so much better. Now on to the language.

Typescript Syntax

Ambients

If you are using something from Javascript such as Document you can declare it and then use it.

declare var document;
document.title = "Hello"; //OK because document has been declared.

Types

any references the Any type, use when Typescript cannot infer the correct type.

number, boolean, string, symbol, void, null, undefined and enums

void means no value - eg function that returns no value. null can be assigned to primitives and objects etc. undefined means not initialised.

var n: number;          // Same as n: number = undefined  
var x = undefined;      // Same as x: any = undefined  
var e: undefined;       // Error, can't reference Undefined type

Symbol

Unique tokens.

var secretKey = Symbol();  
var obj = {};  
obj[secretKey] = "secret message";  // Use symbol as property key  
obj[Symbol.toStringTag] = "test";   // Use of well-known symbol

Arrays

var a: string[] = ["hello", "world"];

Creating an empty array

const emptyInitialState : IUser[] = []

Array type literals:

(string | number)[]  
(() => string)[]

alternatively

Array<string | number>  
Array<() => string>

Array Looping - for of

let someArray = [1, "str", true]
for (let e of someArray) {
  console.log(entry) // 1, "str", true
}

for in

There is an alternative form ‘in’ which returns a list of values of the numeric properties of the object being iterated. eg array index rather than array value. Or properties of any object - the ‘field’ names.

Map as in scala or Java Map

let yourVar: Map<YourKeyType, YourValueType>;
// now you can use it:
yourMap = new Map<YourKeyType, YourValueType>();
yourMap[YourKeyType] = <YourValueType> yourValue;
yourMap.set(key, value);
yourMap.get(key);
yourMap.has(key);
yourMap.delete(key);
yourMap.clear();
yourMap.clear();

Maps are a bit rubbish compared to other modern languages…here is how you iterate over it.

for (let [key, value] of map) {
    console.log(key, value);
}

Tuple Type

They are like arrays which track type and value.

let myTuple:[number, string]=[10,'Macbeth']

Union Types

A parameter can be one of several types

function DoStuff(id:string | number) {
  // id can be a string or a number
}
typeof Type Guard

If there is an operation on one type and not the other you can narrow the type to access that operator.

var n = typeof x === "string" ? x.length : x;  // Type of n is number

Type assertion to narrow the type

let pet = getSmallPet();

if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}

Type predicate return type to narrow the type

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

Anyone who calls this will know the returned type is Fish

Using ‘in’ to narrow the type

“n” in x will narrow the true branch if “n” is a member of x

function move(pet: Fish | Bird) {
    if ("swim" in pet) {
        return pet.swim();
    }
    return pet.fly();
}

Intersection Types

function DoStuff() : Phone & Tablet {
  // the type returned will contain elements from both phone and tablet
}

Handling null

return sn || "default";

Says if sn is defined and not null return it, otherwise return “default”

Option variables and interfaces

Note the ?: in favoriteColor.

interface Friend {  
    name: string;  
    favoriteColor?: string;  
}

function add(friend: Friend) {  
    var name = friend.name;  
}

add({ name: "Fred" });  // Ok  
add({ favoriteColor: "blue" });  // Error, name required  
add({ name: "Jill", favoriteColor: "green" });  // Ok

An option variable can be of type ‘undefined’ so you can use narrowing to remove this from the type.

function foo(c:number, n?:string) {
  if (n) {  // Type of n is string[] | undefined
    console.log(n); // type of n is string[]
  }
}

Bare function signature

interface IAmCallable {  
    (someParam: string): string;  
}

Instances of this interface are callable. Typescript function types are just special cases of Typescript object types. Function types are object types which contain one or more call signatures.

var f: { (): string; };  
var sameType: () => string = f;     // Ok  
var nope: () => number = sameType;  // Error: type mismatch

Statics

Sticking export on the front means it a Module - see modules later.

export class JonathanEg {
  private constructor() {} // ensures no one can create it
  static num:number;
  static show(prefix:string) {
    console.log(prefix+" "+JonathanEg.num)
  }
}

Automatic Properties

The constructor public and private params are made class fields.

class Person {
  constructor(public mutableVal:string, private immutableVal:string)
}

Class member scope

Class members can be public, private, or protected and are either required or optional.

Readonly for immutability

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

Means they cannot be changed outside the class. They should be initialised in the constructor.

Polymorphic this

A fluent API (builder pattern in java) allows method chaining as a function returns itself. (I put this in because it was mentioned on several sites, but really its probably not worth mentioning)

This Type

All classes and function have a this-type, depends on the closest enclosing function, expression, class etc.

Object type literals

You can name them using interface definitions, or leave them anonymous by just providing the block.

{ fld1: number, fld2:string }

Scala has this too, its a terse way to define return types without boilerplate.

Structural subtyping

If a class has the same members as an interface you dont have to say it implements the interface, as typescript will know it has all the members and infer it from the structure. (like GoLang)

interface Thingy {
  name:string;
}
class Person {
  name:string;
  constructor(nm:string) {
    this.name = nm;
  }
}
function foo(t:Thingy) {
  return t.name;
}
foo(new Person("Jonathan"))

Only single inheritance using extends

class Animal {
	constructor(id: number) {…}
}
class Dog extends Animal {
	constructor(id:number) {
	super(id) // has to be first line
}

Note there is a language hack for mixins, which I mention later.

Enums

export enum Colour {
	Red,
	Blue
}
const enum Operator {  
    ADD, DIV,  MUL,  SUB  
}
function compute(op: Operator, a: number, b: number) {
  switch (op) {  
    case Operator.ADD:  
        // execute add  
        break;  
    case Operator.DIV:  
        // execute div  
        break;  
    // ...  
}

Generic Type Params

let kvp : KeyValuePair<string, Animal>=['Fido',doggy1]

interface NamedItem {  
    name: string;  
}

class List<T extends NamedItem> {  
    next: List<T> = null;
    constructor(public item: T) {  
    }

    insertAfter(item: T) {  
        var temp = this.next;  
        this.next = new List(item);  
        this.next.next = temp;  
    }

    log() {  
        console.log(this.item.name);  
    }

    // ...  
}

Conditional Types

T extends U ? X : Y

The type above means when T is assignable to U the type is X, otherwise the type is Y.

Abstract class

abstract class Rocket {
  name : string;

  constructor(name:string) {
    this.name = name;
  }
  abstract takeoff(chanceToExplode : number);
}

A class which implements an abstract class must call super() in the constructor.

Namespaces and export

You can use to provide closure over the things within the namespace. Classes also provide namespaces.

namespace M {  
    var s = "hello";  
    export function f() {  
        return s;  
    }  
}

M.f();  
M.s;  // Error, s is not exported

Destructuring and rest Params

Destructuring can be used when an array is converted into named params. If you want to take an unknown number of params then …

function logStuff([nm1, nm2, ...others]) {

}

Spread operator

let arr = [1,2,3];
let arr2 = [-1,0,...arr];

You can see the … spreads arr into elements in arr2. You can also use it to spread arrays into function params.

Type Alias

// ie a union type which in this case is Shnauzer
let breedCategory : 'Shnauzer' | 'Non-Shnauzer' = 'Shnauzer';

Could instead be done with a type alias for the union Type

type BreedCategory = 'Shnauzer' | 'Non-Shnauzer';
let breedCategory : BreedCategory = 'Shnauzer';

Type checking

if (this instanceof FooBar) {

}

Modules

Everything in a module or namespace are given local scope rather than the default global scope. You can then export them, which mean they can be imported by other bits of code. You can create a module by using export at the top level. You can then import one, many or all things from other modules. file1.ts

export var hi : string = "Hello";
export var t : number = 1;
import {hi} from "./file1"

or

import {hi, t} from "./file1"

or name the modules as its imported

import * as Silly from "./file1"
console.log(Silly.hi);

or import and rename the exported item

import {hi as Hello} from "./file1"

Other forms of export and import

export * as utils from "./utils";   // This is a re-export
default export Foo : string="Jonathan" // only one default export, can be imported
import "./only_for_side_effects.js"; // Only for side effects

Require

You can export a single item from a module, using export =

class Foo{...}
export = Foo;

In typescript this can not be pulled in using require

import FooBar = require("./Foo")

Typescript mixins

This is a language hack as you use implement which is normally used for interfaces, but you are using it to mixin lots of small classes.

class Foo implements Mixin1, Mixin2 {
}

You then add the members from mixin1 and mixin2 to this class. You then write a function called apply mixins which copies the constructors from each mixin into the new class.

function applyMixins(derivedCtor:any, baseCtor:any[]) {
	baseCtors.forEach(baseCtor => {
		Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
		  derivedCtor.prototype[name] = baseCtor.prototype[name];
		});
	});
}

applyMixins(Foo, [Mixin1, Mixin2]);

Declaration Merging

You can define two interfaces with the same name, and the typescript compiler will merge them together as if they were one. You can merge interfaces, Enums, Namespaces, Namespaces with classes. Namespaces with functions. Namespaces with enums. But NOT classes with other classes - use mixins for that.

Module augmentation is perhaps the most useful example of declaration merging.

Maybe you have an existing library holding a class

export class CompanyLib implements Interfaces.Librarian, Employee, Researcher {
	name:string;
}

And you want to extend it, so declare a new module:

declare module './classes' {
	interface CompanyLib {
		phone:string;
		hostMeeting(topic:string):void;
	}
}
CompanyLib.prototype.hostMeeting = function(topic) {
	console.log('foo'+topic);
}

Type Guards

The javascript ‘typeof’ operator - can only use with string, number, boolean or symbol.

let x: string | number = 123;
if (typeof x === 'string') {
	// x is a string, so can use string operators and the compiler wont forget.
}

instanceof - allows you to compare the type of a variable to a type with a constructor, usually classes. It works by looking at the prototype chain. Again the compiler will know you have narrowed the type of the variable and you can now use member functions.

User defined type guards with the format

interface Vehicle { numberOfWheels:number; }

// This function lets the compiler know you now know the type within if blocks.
function isVehicle(v:any): v is vehicle {
	return (<Vehicle>v).numberOfWheels !== undefined;
}

let c = new Car();
If (isVehicle( c )) {
   // it's a vehicle
}

Json parsing

Because Typescript has structural subtyping you can define interfaces with all your fields in and then parse the json string into one of these interfaces. Don’t use classes as the member functions will mess you up good.

import jsonDb from './UnitStatic.json';
interface UnitTypeDetailsStatic {
    unitSizeType: string;
}
interface UnitTypeStats {
    id: number;
    unitSizes: Array<UnitTypeDetailsStatic>;
}
class UnitDatabase {
    public readonly unitsDb : Array<UnitTypeStats>;

    constructor() {
        this.unitsDb = < Array<UnitTypeStats>>jsonDb;
    }
}

Then the json could be in file UnitStatic.json

[
  {
    "id": 0,
    "unitSizes": [
    {
      "unitSizeType": "Troop"
    }
    {
      "unitSizeType": "Regiment"
    }
  }
]

Alternatively, if you want to parse it as a string from a rest call Then

let jsonObj: any = JSON.parse(jsonStr); // string to generic object first
this.unitsDb = <Array<UnitTypeStats>>jsonObj;