TypeScript

Deep Dive

Specification * Recommendations * Patterns


Press Space neo


Originally by BAS (Basarat Ali Syed) / @basarat

What does typescript provide?

  • Strong Typing
  • Better Syntax
Strong Typing --- - Interfaces - Inline typing (type declarations) - Ambients - Variable - Functions - Classes - Modules

Interfaces

What do you want from me?

Interfaces

interface fooInterface{ // constructor new (fooParam1:number,fooParam2?):number; // call signature callable without new (fooParam1:any):string; // indexable [index:string]:number; }

Interfaces

interface fooInterface{ // functions with overloads fooFunc1(fooParam1:bool):any; fooFunc2(...fooParam1:number[]):any; fooFunc3:(fooParam1:bool)=>any; // variables fooVar1:number; fooVar2?:Array; // array fooArr1:{ [index:string]:number; }; fooArr2:number[]; }

Open Ended

interface foo{ x:number; } interface foo{ y:number; } var bar:foo = { x:123, y:123 }

Declaration

What is that?

Type Annotation

var x:number; var y:string; var z:number[]; var foo:{a:any;b:()=>any;} // powerful inline declaration

inline declarations

Offer the complete Interface syntax but inline. Syntax: var __varname__: { __membername__ : __membertype__ ; __membername__ : __membertype__ ; // Repeat } e.g: var x:{ () : Function; [foo:string]: number; new() : string; }

Function signature

// interface / inline interface implementation interface fooInterface{ simpleSyntax():void; lambdaSyntax:()=>void; } var fooInline:{ simpleSyntax():void; lambdaSyntax:()=>void; } Lambda signature is required for function signatures in vars / parameter type declarations.

Function signature for callables

A bit inconsistent. // Simple function, When declaring a var or function arguments var x1 : (s: string)=>string; // When saying something is callable var x2 : { (s: string): string; } interface x3{ (s:string):string; } // Errors: var y1 : (s: string):string; var y2: { (s: string)=> string; } interface y3{ (s:string)=>string; }

Type Inference

var x = 123; var y; y = 123; x = "asdf"; y = "asdf"; var z={foo:123} What type is y? Does the compiler give an error in this code segment?

any

var x:any; var y:number = x; x = y; Can be assigned to anything. Can be assigned anything.

null, undefined

Typescript: For compiler (not runtime) both are type any. var x = null; var y = undefined; Javascript: null is an object instance to mean nothing. undefined is a type as well as a value for something not initialized. var TestVar; alert(TestVar); //shows undefined alert(typeof TestVar); //shows undefined var TestVar = null; alert(TestVar); //shows null alert(typeof TestVar); //shows object

Which string is that?

var obj:String = "123"; var primitive:string = "123"; obj=primitive; // exactly what the first line is doing primitive=obj; // Error Recommendation: Just use string.

Ambients

I thought I saw a pussy cat

Ambients

The magical keyword is ***declare***. Generate no code whatsoever so obviously - no variable initializer - no function bodies / no default parameters

Ambients

declare var angular; declare function foo(takes:string):number; declare class Fancy{ notMuch:string; }

Better Syntax

- Function signatures - Optional parameters - Rest parameters - Default parameters - Arrow Functions - Classes - super - public / private - Modules - Internal - AMD / CommonJS

Functions

are actually functions

Optional

?????? function foo(req:string,optional?:number){}

Default

Effectively optional. Can even do expressions

function foo( req:string, def:number=3, optional=req+def ) { } Generates: function foo(req, def, optional) { if (typeof def === "undefined") { def = 3; } if (typeof optional === "undefined") { optional = req + def; } }

Rest

params denoted by ... - explicitly type declared - have to be last function foo(req:string,...blabla:any[]){} becomes function foo(req) { var blabla = []; for (var _i = 0; _i < (arguments.length - 1); _i++) { blabla[_i] = arguments[_i + 1]; } }

Function overloading

interface IFoo{ test(x:string); test(x:number); }

Function overloading

The actual function should be able to accept any of the overload members. class Foo implements IFoo { test(x: string); test(x: number); test(x: any) { if (typeof x === "string") { //string code } else { //number code } } } The overloads go through the same code generation restrictions e.g you cannot define default values etc. Also the final signature is not directly callable from typescript since its generally an over generalized implementation (any etc).

Function overloading

How to handle variable parameters function f():number; // Error function f(x:any):any{ } function g():number; // Okay function g(x?:any):any{ } Don't need to be in a class.

Function Instances

// global or module function simpleSyntax():void{} var lambdaSyntax1=():void=>{} // class , basically remove function or var class fooClass{ simpleSyntax():void{} lambdaSyntax=():void=>{} } // object literal var fooObject={ simpleSytax: function():void{}, lambdaSyntax: ():void=>{} } // Reminder the lambda based signature was a bit different: // lambdaSyntax:()=>void;

Class

Now called class

Basic

class Foo{ public member:number; static stat:number = 123; constructor(){this.member = 123;} func(){ this.member = 256; } } Note: to access members you ALWAYS have to use this

Basic

var Foo = (function () { function Foo() { this.member = 123; } Foo.stat = 123; Foo.prototype.func = function () { this.member = 256; }; return Foo; })();

Basic

class Foo{ constructor(public member){this.member = 123;} func(){ this.member = 256; } }

Cool: Member not generated

class Foo{ public member:number; } becomes: var Foo = (function () { function Foo() { } return Foo; })();

Interface implementation

Primarily for the person implementing the class. The class can be used in place of the interface even if it doesn't explictly implement that interface. interface iFoo{ x:number; } class Foo1 implements iFoo{ x:number; } class Foo2{ x:number; } var la:iFoo; la = new Foo1(); la = new Foo2();

Syntax Limitations

Not everthing that can be declared in an interface can be implemented by a Typescript class e.g. Indexible, call signatures

interface WidgetMap { [name: string]: Widget; } var map: WidgetMap = {}; map['gear'] = new GearWidget(); var w = map['gear']; // w is inferred to type Widget

Syntax Limitations

Callable example

interface Foo { (): any; (value: any): void; } function createFoo(): Foo { var getFunc = () => { console.log("get"); return "foo"; } var setFunc = (value: any) => { console.log("set"); } return (value?: any) => { if (value) { setFunc(value); } else { return getFunc(); } } } var f = createFoo(); f("bar"); f();

Optional members?

interface foo{ x?:number; } class boo1 implements foo{ } class boo2 implements foo{ x:number; }

Class Inheritance

super : Not the Australian kind.

Basic

class FooBase{ } class FooChild extends FooBase{ }

Basic

var __extends = this.__extends || function (d, b) { function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); }; var FooBase = (function () { function FooBase() { } return FooBase; })(); var FooChild = (function (_super) { __extends(FooChild, _super); function FooChild() { _super.apply(this, arguments); } return FooChild; })(FooBase);

Static is Broken

var __extends = this.__extends || function (d, b) { function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); }; vs. var __extends = this.__extends || function(d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function f() { this.constructor = d; } f.prototype = b.prototype; d.prototype = new f(); } work item

super

class base { test(foo:number){ console.log(foo); } } class child extends base{ constructor(public x:number){ super(); super.test(x); // 15 this.test(x); // 25 } test(foo:number){ console.log(foo+10); } } var test=new child(15);

super

super called via call
super (dot) goes directly to prototype function child(x) { _super.call(this); this.x = x; _super.prototype.test.call(this, x); this.test(x); }

Super Cautious

class A{ fooMem=10; } class B extends A{ constructor(){ console.log(this.fooMem); // undefined super(); console.log(this.fooMem); console.log(super.fooMem); // undefined } }; var test = new B(); Make super first call and only use for function access afterwords.

this

which one?

Save me

Typescript knows. But doesn't mean it will save you. - Constructor / member functions - *this* a type of containing class - Static functions - *this* is of type constructor function - All other places its any

Err

Typescript will complain if you miss this. It will say member not defined. So novice way is to put in this. class Example{ member = 10; constructor(){ // Could have been an jquery call // you were porting from js setTimeout(function() { alert(this.member); },100); } } var ex = new Example(); But this code is wrong since "this" is any and not the class instance.

Arrow Functions

// all of these are the same var test1=(x:number)=>{return x*10}; var test2=(x:number)=>(x*10); var test3=(x:number)=>x*10; var test4=(x)=>x*10; var test5=x=>x*10; // {} require a return statement // This is actually returning undefined // which is a valid number and fails silently var test6=(x:number)=>{x*10};

Arrow Functions

For lexical scoping. var foo = () => {return this;} generates: var _this = this; var foo = function () { return _this; };

Err Fix

class Example{ member = 10; constructor(){ // Could have been an jquery call // you were porting from js setTimeout(()=>{ alert(this.member); },100); } } var ex = new Example();

So basically

Replace all functions in arguments with ()=>

Almost

Keep functions for: - When you want this to be any. E.g. $.each. - When you need both. Then resort to the manual closure trick of var self = this;

Pattern

When your class is just a collection of functions other people would call. class Example{ func1:(number)=>void; func2:()=>number; member:number = 10; constructor(){ this.func1=(x)=>{this.member=x}; this.func2=()=>this.member; } } The body needs to seperate from declaration since this is only available in constructor / member functions.

Useful for libs like knockout.

Internal Module

Fetch boy!

Internal Modules

Like namespaces in C# but it is actually a singleton instance. module M{ var s = "test" export function f(){ return s; } } Just the export keyword

Internal Modules

The variables inside the variables are only available to the current body of the module via closure. // Generated var M; (function (M) { var s = "test"; function f() { return s; } M.f = f; })(M || (M = {})); I say current body since modules are also open ended and can therefore have more than one bodies.

Internal Modules

module M{ export var s = "test" } module M{ export var n = 123; } generates: var M; (function (M) { M.s = "test"; })(M || (M = {})); var M; (function (M) { M.n = 123; })(M || (M = {}));

Quick nesting

module A.B.C{ } // same as module A{ export module B{ export module C{ } } } And you can have this side by side as modules are open ended.

Aliasing

module x.y.z{ export var n =10; } // Alias import foo = x.y.z; console.log(foo.n); generates: var foo = x.y.z; console.log(foo.n);

Careful with naming

module Test{ export class Test{ } } var Test; (function (Test) { var Test = (function () { function Test() { } return Test; })(); Test.Test = Test; })(Test || (Test = {})); Tip: Do not reuse the module name inside the module

Declaration Spaces

- Types are contributed by interface , module , class. - Class and module also contribute a variable (constructor for class , instance for module)

Declaration Spaces

"The name does not exist in the current scope". module M { export interface P {} } import im = M; var foo1:im.P; // Okay var vm = M; var foo2:vm.P; // Error You will still get autocomplete.

Structural Typing

Perhaps I can give you something else?

Class have their own brand

interface iFoo{ x:number; } class Foo1 implements iFoo{ x:number; } class Foo2{ x:number; } var la:iFoo; la = new Foo1(); la = new Foo2(); var fa:Foo1 = new Foo2(); //Error

That brand is too mainstream

Everything else modules, interfaces, inline declarations and inferred structures are behaviour free. interface inter{ foo:number; } module mod{ export var foo:number; } var w:inter; var x:mod; var y:{foo:number;} var z={foo:123} w=x=y=z; z=y=x=w;

Its okay to have more

interface inter{ foo:number; } var x:inter; var y={ foo:123, la:23 }; x = y; y = x; //Error

Extending Built-ins

You get it, I get it, But does the compiler get it?

Built-in Interfaces

Present in lib.d.ts that ships with the typescript compiler. interface Array { toString(): string; toLocaleString(): string; concat(...items: _element[][]): _element[]; concat(...items: _element[]): _element[]; join(seperator?: string): string;

Extend them

Its just javascript interface Array { shuffle: () => any; // <-- Whatever signature you want. } Array.prototype.shuffle = function () { ... }; Prototype is an implicitly available static variable on any type and cannot be declared manually.

Type Assertion

Cause I said so!

As simple as it gets

// error var x:HTMLCanvasElement = document.getElementById("canvasId"); // valid var y:HTMLCanvasElement = < HTMLCanvasElement > document.getElementById("canvasId"); // type inferred var z = < HTMLCanvasElement > document.getElementById("canvasId"); Should not be called type casting.

Interfaces FTW

1,2,3, Bottoms up!

JQuery 1

declare var $: any; Any

JQuery 2

declare var $: JQueryStatic; interface JQueryStatic { // AJAX ajax(...params:any[]); Start with rest parameters.

JQuery 3

declare var $: JQueryStatic; interface JQueryStatic { // AJAX ajax(settings: JQueryAjaxSettings); ajax(url: string, settings: JQueryAjaxSettings); interface JQueryAjaxSettings { accepts?: any; async?: bool; beforeSend? (jqXHR: JQueryXHR, settings: JQueryAjaxSettings); cache?: bool; Use optional liberally.

Its open ended

interface JQueryStatic { // More traditional members: Callables (selector: string, context?: any): JQuery; (element: Element): JQuery; (object: {}): JQuery; (elementArray: Element[]): JQuery; (object: JQuery): JQuery; (func: Function): JQuery; (): JQuery; interface JQuery { attr(attributeName: string): string; attr(attributeName: string, value: any): JQuery; html(htmlString: string): JQuery; html(): string; Plugin: interface JQuery { myPlugin(): JQuery; }

Multiple Optionals

Jquery: .fadeToggle( [duration ] [, easing ] [, complete ] ) TypeScript: fadeToggle(duration?: any, callback?: any): JQuery; fadeToggle(duration?: any, easing?: string, callback?: any): JQuery;

Pattern

Modelling Statics

Sample Class

Suppose you have javscript class with equivalent behaviour: class Test{ static foo = 123; bar = 456; }

Simple solution

What the compiler will generate with --declaration flag: declare class Test { static foo: number; public bar: number; } Not open ended!

Manual solution

Remember declaration spaces? Add one to to each space: // Non static members interface Test{ bar:number; } // Static members and constructors go here: interface TestStatic { new():Test; foo:number; } declare var Test:TestStatic; And its open ended.

Finally

One Final Note

This presentation uses TypeScript

This presentation uses TypeScript with Javascript (RequireJS + AngularJS) and encourage you to look at the source on github.

THANK YOU

BAS

(Basarat Ali Syed)

bas AT basarat.com

basarat.com
@basarat
Fork me on GitHub