LearningTypeScript

From HerzbubeWiki
Jump to navigation Jump to search

TypeScript is a superset of JavaScript which adds optional static typing to the language. TypeScript requires a compiler. The compiler output is plain JavaScript.

On this page I keep notes about my first steps with TypeScript. Companion pages to this one are LearningJavaScript and LearningAngular.


References


Basics

  • TypeScript is a specialization, or superset, of JavaScript. All JavaScript programs therefore are also TypeScript programs.
  • TypeScript requires a compiler that transforms the TypeScript code into JavaScript that can then be executed by runtimes such as browsers.
  • TypeScript files use the extension .ts.


Typing

Static typing

Example how type annotations provide static typing:

var message:string = "Hello World" 

function add(left:number, right:number): number
{
  return left + right;
}

// The compiler uses type inference if no type is specified.
// Here it infers that the subtract() function returns a number
// because the two operands left and right are also numbers.
function subtract(left:number, right:number)
{
  return left - right;
}


Primitive types:

  • number: Double precision 64-bit floating point values. It can be used to represent both, integers and fractions.
  • string: Sequence of Unicode characters.
  • boolean: Logical values true and false.


Other types:

  • void: Used to declare the return type of functions that do not return a value.
  • Array
  • Enums


Even more types:

  • Tuple: An array with a predefined data type at each index position.
  • Union: A variable that holds more than one type of data.
  • never: To be used when you are sure that something is never going to occur. TODO: More info needed.
  • any: The supertype of all data types. Is used for weakly or dynamically typed structures. Is also used when a type is not specified and the compiler fails to infer the type.


Type assertion / type casting

In TypeScript you can force the compiler to see a variable as having a different type by so-called "type assertion", like this:

var foo = '1' 
var bar:number = <number> <any> foo
console.log(typeof(bar))


The special term "type assertion" is used because the term "type casting" that is known from other languages implies that there is some sort of conversion going on at runtime. That is not the case here.


Data Structures

Arrays

Use brackets as suffix to the type name to declare an array:

var foo:type[]

Use brackets surrounding a list of values to assign an array literal:

var foo:number[] = [1, 2, 3]

Use brackets and an index position to access array elements:

var bar = foo[0];  // zero-based index

Multi-dimensional arrays are straightforward:

var foo:type[][] = [ [1, 2, 3], [4, 5, 6] ]


Tuples

Tuples are like arrays, except they store values of different types at each index position. Examples:

var tuple1 = [42, "foo"];
var element1 = tuple[0];  // zero-based index

// Empty tuple
var tuple2 = [];
// Initialize the tuple
tuple2[0] = 42;
tuple2[1] = "foo";


Unions

Unions are declared with a pipe character ("|") separating the possible types:

var union1:string|number;
union1 = 42;
union1 = "foo";

// Can be either a string value, or an array of numbers
var union2:string|number[];


Functions

The general syntax to declare a function. As can be seen the type declaration for parameter and return types is optional.

function function_name (param1[:type], param2[:type], param3[:type]) [:type]

Optional parameters can be declared with a question mark ("?") after the parameter name. Optional parameters must come after non-optional parameters. Example:

function foo(bar:number, baz?:string)

Variable number arguments are called "rest parameters" in TypeScript. These are declared with three periods ("...") prefixing the parameter name and the parameter type must be an array type. Rest parameter values passed must all be of the same type. A rest parameter must come after the other non-rest parameters. Example:

function foo(bar:number, ...baz:string[])

Default parameter values are declared with the usual "assignment" syntax. Example:

function foo(bar:number, baz:string = "a default value")


Object orientation

Interfaces

Here's an interface example. Note that interfaces when they are compiled do not have a representation in JavaScript. Also note that an interface may contain fields, not just functions!

interface IFoo
{
  firstName:string,
  lastName:string,
  doIt: () => string
}

var aFoo:IFoo =
{
  firstName:"First name",
  lastName:"Last name",
  doIt: ():string => {return "Yay!"}
}

An interface can extend one or even multiple other interfaces using the "extends" keyword:

interface IFoo { ... }
interface IBar { ... }

interface IBaz extends IFoo, IBar { ... }


Classes

Example how to declare and instantiate a class:

class HelloWorld
{
  message:string = "Hello World";

  // The "constructor" keyword comes from JavaScript
  constructor(message:string)
  {
    this.message = message;
  }

  sayIt(): void
  {
    console.log(this.message)
  }
}

var helloWorld = new HelloWorld();
helloWorld.sayIt();


A class can extend another class using the "extends" keyword. Just like JavaScript, TypeScript also does not support multiple inheritance.

class Foo { ... }

class Bar extends Foo { ... }

A class implements an interface using the "implements" keyword:

interface IFoo { ... }

class Bar implements IFoo { ... }

Classes can have static members. A static member is accessed using the class name.

class Foo
{
  static aNumber:number;
  static aFunction():void
  {
    console.log(Foo.aNumber);
  } 
}


Visibility

TypeScript supports the definition of visibility for class members. The usual three visibilites public/protected/private exist.

class Foo
{
  // Default visibility is public!
  aNumber:number;

  public anotherNumber:number;
  protected aThirdNumber:number;
  private aFourthNumber:number;
}


Operators

Non-null assertion operator "!"

The non-null assertion operator "!" is used as a postfix operator. Appending it after an expression tells the TypeScript compiler that the preceding expression cannot be null or undefined. In the generated JavaScript code the operator is simply omitted. See the TypeScript 2.0 release notes.

Example:

function foo(value: string): void { ...}

// Here we tell the compiler that properties "b" and "c" cannot be null or undefined
foo(a.b!.c!);


Optional property access operator "?."

The optional property access operator "?." is shorthand for writing conditional code that evaluates to undefined when an object is null or undefined, and evaluates to a property value if it is not. See the TypeScript 3.7 release notes.

Example:

let foo = bar?.baz;

is roughly equivalent to

let foo;
if (bar === null || bar === undefined) { foo = undefined; }
else                                   { foo = bar.baz; }


Nullish coalescing operator "??"

The nullish coalescing operator "??" is shorthand for writing conditional code that does something when an expression is null or undefined, and evaluates to the expression value if it is not. The difference to the "||" operator is that "??" only acts upon null or undefined, but not on other falsy values. See the TypeScript 3.7 release notes.

Example:

let foo = bar ?? baz;

is roughly equivalent to

let foo;
if (bar === null || bar === undefined) { foo = baz; }
else                                   { foo = bar; }


Generics

TypeScript support generic programming. Example:

function foo<T>(aParameter: T): T
{
  return aParameter;
}


Code organization

Namespaces

Unlike JavaScript, TypeScript supports namespaces. Namespaces can be nested. Sub-namespaces, interfaces and classes that should be usable from outside a namespace must be declare with the "export" keyword:

namespace Namespace1
{
  export namespace Namespace2
  {
    export interface IFoo { ... }
    export class Bar { ... }
  }
}

var aBar = new Namespace1.Namespace2.Bar();

If a namespace is declared in a different .ts file, then a special syntax is needed:

--- file1.ts ---
namespace Namespace
{
  export class Bar { ... }
}

--- file2.ts ---
/// <reference path = "file1.ts" /> 
var aBar = new Namespace.Bar();


Modules

There used to be a distinction between internal and external modules. Internal modules have now been superseded by namespaces, only external modules remain. We simply talk about them as "modules".


Modules are basically files.

  • When you want to make something available in a module you declared it with the keyword "export".
  • When you want to consume something that has been made available in a module you use the keywords "import" and "require".


Example:

--- file1.ts ---
export interface IFoo { ... }

--- file2.ts ---
// Don't specify the file extension
import file1Module = require("./file1");
class Bar implements file1Module.IFoo { ... }


Declaration files / ambient declarations

The compiler can generate a so-called "declaration" file when it compiles a .ts file. This is like a header file in C/C++ and contains merely the interface to the stuff (functions, classes) that is in the .ts file.

Declaration files have the extension .d.ts.

If you have an external JavaScript library you can write your own .d.ts declaration file for it. Apparently this is called an "ambient declaration". Example syntax to write and consume an ambient declaration:

--- file1.d.ts ---
declare module Foo
{
  export class Bar { ... }
}

--- file2.ts ---
/// <reference path = "file1.d.ts" />
var aBar = new Foo.Bar();


Other syntactic elements

Decorators

Decorators (link to handbook) are special declarations attached to other declarations: Classes, methods, accessors, properties, or parameters.

Decorators are expressions prefixed with an "@" character. Multiple decorators can be attached to the same declaration. Example:

@foo @bar BazClass { ... }

A decorator must evaluate to a function that will be called at runtime with information about the decorated declaration. So at runtime this happens:

  • Decorator expressions are evaluated first-to-last
  • The results of the evaluation are then called as functions, again first-to-last

The evaluation step allows to write decorator factories that generate the actual function that will be called.

Full-fledged example:

// Factory methods
function foo() {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { ... };
}
function bar() {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { ... };
}

// Decorated code
class BazClass {
  @foo()
  @bar()
  aMethod() { ... }
}

The subject is too big to fully cover here, for more details refer to the TypeScript Handbook (link see above). Decorators are heavily used in Angular, for instance.

Decorators are an experimental feature that needs to be enabled explicitly in tsconfig.json with this option:

 "experimentalDecorators": true


Compiler

  • The TypeScript compiler is named tsc.
  • The compiler is written in TypeScript. As a result, it can be compiled into regular JavaScript and can then be executed in any JavaScript engine (e.g. a browser).
  • The compiler package comes bundled with a runtime (a so-called "script host") that can execute the compiler.
  • The compiler is available as a Debian package node-typescript
  • The compiler is also available as a Node.js package that uses Node.js as the runtime.


Infrastructure / Conventions

tsconfig.json

The presence of a tsconfig.json file indicates that the folder is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project.

When you start to compile by running tsc with no arguments, the compiler searches for the tsconfig.json file starting in the current folder, then continuing up the parent folder chain.

For details see the handbook.


Project initialization

Run this command

tsc --init

to create an initial tsconfig-json file. You'll need to edit it to suit your needs.