Skip to content

TypeScript

TS

Config, WebPack

https://webpack.js.org/guides/typescript/

Or using babel

https://github.com/Microsoft/TypeScript-Babel-Starter#create-a-webpackconfigjs

Typical options for TS - tsconfig.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
    "compilerOptions": {
    "module": "esnext",
    "target": "es2017",
    "strict": true,
    "alwaysStrict": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "removeComments": true,
    "moduleResolution": "node",
    "lib": [
        "ES5","ES6","ES7","ES2017",
        "ES2018","ES2019","ES2020",
        "ESNext","DOM","DOM.Iterable",
    ]
    },
    "include": [
        "global.d.ts", "./src/**/*"
    ],
    "exclude": [
        "node_modules",
    ]
}

Vite - the latest builder

1
2
3
4
5
6
7
npm create vite@latest my-ts-app -- --template vanilla-ts

cd my-ts-app
npm install
npm run dev

code .

write some code and then to inspect build result

1
npm run build

Type Checking

OK in pure js.

1
2
3
4
5
function add(a, b) {
    return a + b;
}

let ab = add("a", 5);

TS will have several compile errors

1
2
3
4
5
6
function add(a: number, b: number): string {
    return a + b;
}

let sum = 5;
sum = add("a", 5);

Full types defined.

1
2
3
4
5
function add(a: number, b: number): string {
    return (a + b).toString();
}
let sum: string = "5";
sum = add(1, 5);

Or using type inference

1
2
3
4
5
function add(a: number, b: number) {
    return (a + b).toString();
}
let sum = "5";
sum = add(1, 5);

Basic types

boolean, number, string, string interpolation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let isDone: boolean = false;

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

let fullName: string = `Andres Käver`;
let born: number = 1974;
let sentence1: string = `Hello, my name is ${ fullName }.

I am ${ 2020 - born } years old.`;

let sentence2: string = "Hello, my name is " + fullName + ".\n\n" +
"I am " + (2020 - born) + " years old.";

Basic types – array, tuple

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["akaver", 1974]; // OK

// Initialize it incorrectly
// Type ‘x' is not assignable to type ‘y'
x = [1974, "akaver"]; 

console.log(x[0].substring(1)); // OK

// Error, Property 'substring’ 
// does not exist on type 'number'
console.log(x[1].substring(1)); 

// Error, Tuple type '[string, number]’ 
// of length '2' has no element at index '3'.
x[3] = "world"; 

Enum

  • Fixed set of values (typically numbers)
  • Values start from 0, can be specified
  • Provides maping in both directions
  • String enums
    • No maping
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
enum Color1 {Red, Green, Blue}
let c1: Color1 = Color1.Green;

enum Color2 {Red=1, Green=2, Blue=4}
let c2: Color2 = Color2.Green;

// enum reverse mapping
let colorName: string = Color1[c1];
let colorCode: number = Color1['Red'];

enum Names {foo='Foo', bar='Bar'}
let n: Names = Names.foo;

Any

  • Opt-out of type checking and let the values pass through compile-time checks.
  • Return to plain JS approach – type is associated with value. No type checking by TS compiler.
  • Useful when working with external sources (json, html, libraries)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let notSure: any = 4;

// ifItExists might exist at runtime
notSure.ifItExists(); 

// toFixed exists (no compiler check)
notSure.toFixed(); 

notSure = "maybe a string instead";
// definitely a boolean
notSure = false; 

let list: any[] = [1, true, "free"];

Void

Opposite of any.
Usually used as return type from functions – that don’t return anything.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function testVoid(): void {
    console.log("foobar");
    // only undefined is allowed
    return undefined;
}

let voidResult = testVoid();
console.log(voidResult); //undefined
voidResult = undefined;
voidResult = null; // error

null, undefined

  • In TypeScript, both undefined and null actually have their own types named undefined and null respectively. 
  • null and undefined are only assignable to any and their respective types (the one exception being that undefined is also assignable to void).

union types

If variable/parameter etc must have several possible types associated with it - union types

1
2
3
4
var s: string | null | undefined = "foo";
s = null;
s = undefined;
s = 5; //error

never

  • The never type represents the type of values that never occur.
  • For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns.
  • Variables also acquire the type never when narrowed by any type guards that can never be true.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Function returning never must have unreachable end point
function error(message: string): never {
    throw new Error(message);
}

// Inferred return type is never
function fail() {
    return error("Something failed");
}

// Function returning never must have unreachable end point
function infiniteLoop(): never {
    while (true) {
    }
}

object

object is a type that represents the non-primitive type, i.e. anything that is not number, string, boolean, bigint, symbol, null, or undefined.

1
2
3
4
5
6
7
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

type assertions (casting)

  • Sometimes you know more than TS compiler. :-)
  • Type assertions are a way to tell the compiler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.
  • as-style is the only one supported in JSX.
    1
    2
    3
    let someValue: any = "this is a string";
    let strLength1: number = (<string>someValue).length;
    let strLength2: number = (someValue as string).length;
    

Interfaces

  • TS type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”.
  • In TS, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.
    1
    2
    3
    4
    5
    6
    7
    8
    interface Person {
        name: string;
    }
    function printLabel(p: Person) {
        console.log(p.name);
    }
    let myPerson = {name: "akaver", age: 10};
    printLabel(myPerson);
    

Interfaces, optional properties

  • Not all properties of an interface may be required. Some exist under certain conditions or may not be there at all.
  • Interfaces with optional properties are written similar to other interfaces, with each optional property denoted by a ? at the end of the property name in the declaration.

Interfaces, readonly

  • Some properties should only be modifiable when an object is first created. You can specify this by putting readonly before the name of the property.
  • TypeScript comes with a ReadonlyArray<T> type that is the same as Array<T> with all mutating methods removed, so you can make sure you don’t change your arrays after creation.
  • Const vs readonly
    • Variables use const whereas properties use readonly.

Interfaces, Excess Property Checks

  • Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments.
  • If an object literal has any properties that the “target type” doesn’t have – error.
  • Use index signature
    • Object could also have any number of other properties
1
2
3
4
5
6
7
8
9
interface Person {
    readonly name: string;
    age?: number;
    [propName: string]: any;
}
function printLabel(p: Person) {
    console.log(p.name);
}
printLabel({name: "akaver", foo: "Bar"});

Interfaces, function types

  • To describe a function type within an interface, give the interface a call signature.
  • This is like a function declaration with only the parameter list and return type given.
  • Each parameter in the parameter list requires both name and type.
    1
    2
    3
    interface SearchFunc {
        (source: string, subString: string): boolean;
    }
    

Interfaces, Class

  • One of the most common uses of interfaces in languages like C#, that of explicitly enforcing that a class meets a particular contract, is also possible in TypeScript.
  • Like classes, interfaces can extend each other.
  • An interface can extend multiple interfaces.
  • When an interface type extends a class type it inherits the members of the class but not their implementations. Both private and public.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date): void;
}
class Clock implements ClockInterface {
    currentTime: Date = new Date();
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

Classes

  • Traditional JS does not have classes – they where introduced with ES6 (2015). TS has always supported them.
  • Few changes compared to ES6
    • Declare basic properties
    • Public, private (and JS # private) and protected modifiers

Classes – public, private, protected

  • Public by default.
  • Private – cannot be accessed outside from its containing class.
  • The protected modifier acts much like the private modifier with the exception that members declared protected can also be accessed within deriving classes.
  • TS is a structural type system. Comparing two different types, regardless of where they came from, if the types of all members are compatible – types themselves are compatible.
  • Comparing types that have private and protected members – for types to be considered compatible private members have to be originated in the same declaration. The same applies to protected members.

Classes – readonly, parameter

  • Properties can be made readonly by using the readonly keyword.
  • Readonly properties must be initialized at their declaration or in the constructor.
  • Parameter properties let you create and initialize a member in one place.
  • Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier or readonly, or both. Using private for a parameter property declares and initializes a private member; likewise, the same is done for public, protected, and readonly.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Point {
    private x: number;
    private y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y
    }
}

// vs Parameter properties

class Point {
    constructor(public x: number, public y: number) {
    }
}

Classes – Static, Abstract

  • static members of a class, those that are visible on the class itself rather than on the instances of it.
  • Each instance accesses this value through prepending the name of the class.
  • Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly.
  • Unlike an interface, an abstract class may contain implementation details for its members. The abstract keyword is used to define abstract classes as well as abstract methods within an abstract class.

Functions

Function type

1
2
let myAdd: (baseValue: number, increment: number) => number  
    = function (x: number, y: number): number { return x + y; };

  • Optional and Default Parameters
    • TS requires that parameter types and their count match (not like JS)
  • Optional parameter – add ? at the end of parameter name.
  • Any optional parameters must follow required parameters.
  • Default value that a parameter will be assigned if the user does not provide one, or if the user passes undefined in its place - default-initialized parameters.
  • Default-initialized parameters that come after all required parameters are treated as optional

Functions - this parameter

  • In JavaScript, this is a variable that’s set when a function is called. This makes it a very powerful and flexible feature, but it comes at the cost of always having to know about the context that a function is executing in.
  • Provide an explicit this parameter. this parameters are fake parameters that come first in the parameter list of a function.
    1
    2
    3
    4
    5
    function numberPressed(this: GlobalEventHandlers, event: Event) {
        let elem = <HTMLAnchorElement>this;
        let key = elem.dataset.value;
        display!.innerHTML = calcBrain.handleKey(key!);
    }
    

Generics, constraints

Generics are similar to C# generics

1
2
3
4
5
6
7
8
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
loggingIdentity({length: 10, value: 3});

Types (advanced)

  • Intersection – glue types together
    • typeA & typeB
  • Union – either one or the other

    • typeA | typeB
  • Type assertions for several times – not nice!

  • Need some way to differentiate between types.
  • A common idiom in JavaScript to differentiate between two possible values is to check for the presence of a member (truthiness).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
interface Bird {
    fly(): any;
    layEggs(): any;
}
interface Fish {
    swim() : any;
    layEggs(): any;
}
function getSmallPet(): Fish | Bird {
    //...
}
let pet = getSmallPet();
if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}
  • Type Guards
1
2
3
4
function numberPressed(this: GlobalEventHandlers, event: Event) {
    let elem = <HTMLAnchorElement>this;
    let key = elem.dataset.value;
}
  • Using type predicates
1
2
3
4
5
6
7
8
function isHTMLAnchorElement(elem: GlobalEventHandlers): elem is HTMLAnchorElement {
    return (elem as HTMLAnchorElement).href !== undefined;
}
function numberPressed2(this: GlobalEventHandlers, event: Event) {
    if (isHTMLAnchorElement(this) && this.dataset.value){
        display!.innerHTML = calcBrain.handleKey(this.dataset.value);
    }
}
  • typeof x === “basictype”
    • These typeof type guards are recognized in two different forms: typeof v === "typename" and typeof v !== "typename", where "typename" must be "number", "string", "boolean", or "symbol".
  • x instanceof ConstructorFuction
    • The right side of the instanceof needs to be a constructor function, and TypeScript will narrow down to:
    • the type of the function’s prototype property if its type is not any
    • the union of types returned by that type’s construct signatures

Type alias

Type aliases create a new name for a type. Type aliases are sometimes similar to interfaces, but can name primitives, unions, tuples, and any other types

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === "string") {
        return n;
    }
    else {
        return n();
    }
}

Types - literals

Allows to use only predefined values.

1
2
type Easing = "ease-in" | "ease-out" | "ease-in-out";
type Duration = 100 | 200 | 400;

Globals

  • Global variables
    • Use declare var to declare variables. If the variable is read-only, you can use declare const. You can also use declare let if the variable is block-scoped.
  • Global functions
    • Use declare function to declare functions.
  • Global objects
    • Use declare namespace to describe types or values accessed by dotted notation

Proxy (ES6)

  • The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
  • handler — the placeholder object which contains the trap(s)
  • traps — the method(s) that provide property access
  • target — object which the proxy virtualizes
    1
    2
    3
    4
    let proxy = new Proxy({}, {
        set(target, property, value) { target[property] = value; return true },
        get(target, property) { return target[property] }
    });
    

tsconfig.json

Typical options for TS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
    "compilerOptions": {
    "module": "esnext",
    "target": "es2017",
    "strict": true,
    "alwaysStrict": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "removeComments": true,
    "moduleResolution": "node",
    "lib": [
        "ES5","ES6","ES7","ES2017",
        "ES2018","ES2019","ES2020",
        "ESNext","DOM","DOM.Iterable",
    ]
    },
    "include": [
        "global.d.ts", "./src/**/*"
    ],
    "exclude": [
        "node_modules",
    ]
}