Aug 26, 2015

Minimize the price (not the benefit) of type in Typescript

I watched Anders Hejlsberg introducing typescript in 2012. Then I thought I don't have the problem it tries to solve. Type in Javascript has not yet caused any problem for me, as I have unit test, renaming and intellisense is also not a problem for me, because I have webstorm. Then, I found that Douglas Crockford said something in google plus

Microsoft's TypeScript may be the best of the many JavaScript front ends. It seems to generate the most attractive code. And I think it should take pressure off of the ECMAScript Standard for new features like type declarations and classes. Anders has shown that these can be provided nicely by a preprocessor, so there is no need to change the underlying language.

I think that JavaScript's loose typing is one of its best features and that type checking is way overrated. TypeScript adds sweetness, but at a price. It is not a price I am willing to pay.

I was kind of thinking the same way. So I didn't use Typescript in my all my projects until 4 months ago, when I joined a angularjs project where typescript is designated to be used. Now I feel that, type can be just a ceremony if it is not used properly, but it can be good enhancement of Javascript if used in the right way. In the following, I will list some of bad and good example of using typescript. The good examples can reduce the cost of using type without losing its benefit.

Don't annotate type for variable with value of built-in type, use inference.

//bad
var n: number = 7;
var s: string = 's';
var re: RegExp = /\s+/;
var itemsOfString: string[] = ['one', 'two', 'three'];

var callbacks: ((input: string) => number)[] = [
  function (a: string) {
    return Number(a);
  },
  function (b: string) {
    return Number(b);
  }];


//good, typescript can inference the type from value
var n = 7;
var s = 's';
var re = /\s+/;
var itemsOfString = ['one', 'two', 'three'];

var callbacks = [
  function (a: string) {
    return Number(a);
  },
  function (b: string) {
    return Number(b);
  }];

Use type annotation sparingly for custom type.

interface IPerson {
   firstName: string,
   lastName: string
}

function printPerson(person:IPerson) {
  console.log(person.firstName + ',' + person.lastName);
 }

//bad, this annotation is unnecessary
printPerson(<IPerson>{
 firstName: 'John',
  lastName: 'Doe'
});

//good, because the annotation can give you intellisense
var john: IPerson = {
  firstName: 'John',
  lastName: 'Doe'
};

printPerson(john);

//good, intellisense is available from inference
printPerson({
 firstName: 'John',
  lastName: 'Doe'
});

Don't create unnecessary interface or class if it is one-off thing, use inference

//bad, you need to duplicate type information from the object
interface Customer {
    firstName: string,
    lastName: string    
}
function createCustomer():Customer {
  return {
     firstName: 'John',
     lastName: 'Doe'
   };
}

//good, typescript compiler can infer the return type
function createCustomer(){
  return {
     firstName: 'John',
     lastName: 'Doe'
   };
}

Don't create interface if "named anonymous type" is available

//bad, you need to duplicate the type information from the object
namespace demo {
     
    export interface Settings {
          svcUrl: string
    }

    angular.module("demo").value("settings", {
         svcUrl: 'http://foo.com'
    });
}
//good
namespace demo {
    var settings = {
         svcUrl: 'http://foo.com'
    };

    export type Settings = typeof settings;
    angular.module("demo").value("settings", settings);
}
//
namespace demo {
    //use settings with type annotation 
    angular.module('demo').controller('main', function (settings: Settings) {
       console.log(settings.svcUrl);
    });
}

Don't create class if all you need is an interface, because typescript is using duck typing

//bad
class Customer {
    firstName: string;
    lastName: string ;
}

//good
interface Customer {
    firstName: string;
    lastName: string ;
}

function logCustomer(cust: Customer) {
   console.log(cust.firstName + ', ' + cust.lastName);
}

Don't use multiple assignment to define an object, use object literal expression

//bad, because typescript cannot infer the type of return object
function createCustomer () {
   var rtn : any = {};
   rtn.firstName = 'John';
   rtn.lastName = 'Doe';
   return rtn;
}

//good, because typescript can infer the literal object
function createCustomer () {
    return {
        firstName: 'John',
        lastName: 'Doe'
    }
}

Don't use module if it is internal module, use namespace

//bad 
module demo {

}

//good
namespace demo {

}

Don't use complicated construct, if simple construct is good enough

namespace demo {
    //bad
    export class Data {
        public static get key1(): string { return "value1"; }
        public static get key2(): string { return "value2"; }
    }

    //good
    export var data = {
        key1: 'value1',
        key2: 'value2',
    }
}