Playing with Typescript Basic
If you are here, I assume you already convinced that Typescript is useful, and how it can help you to write better javascript projects.
The best way to learn Typescript is to dive in. In this article, I take you through some of the basic features of Typescript.
Assuming that you already installed node.js >= in your machine, the next step is to install Typescript:
$ npm install typescript -g
Ensure that the typescript is installed correctly:
$ tsc --version
Create a new .ts
file
$ vi index.ts
And then add the following listing:
function timeout(n: number) {
return new Promise(res => setTimeout(res, n));
}
export async function multiplyNumbers(a: number, b: number) {
await timeout(500);
return a * b;
}
(async () => {
console.log(await multiplyNumbers(3, 4));
})();
The aboved code is an example of typescript code that uses async function, one of the features of modern Javascript. The timeout()
function takes one parameter (time) and will wait "time" ms. Now, we want to transpile it into ES2015 and can be run in Node.js environment:
tsc index.ts --target ES2015 --module commonjs
You would get a generated index.js
file with the following code:
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.multiplyNumbers = void 0;
function timeout(n) {
return new Promise(res => setTimeout(res, n));
}
function multiplyNumbers(a, b) {
return __awaiter(this, void 0, void 0, function* () {
yield timeout(500);
return a * b;
});
}
exports.multiplyNumbers = multiplyNumbers;
(() => __awaiter(void 0, void 0, void 0, function* () {
console.log(yield multiplyNumbers(3, 4));
}))();
You can see that the aboved code has implementation of async function because ES2015 doesn't have this feature. Now, try to transpile the index.ts
code to ES2017.
$ tsc index.ts --target ES2017 --module commonjs
This is the result. You can see that there is not much of a difference with the source as all the modern javascript features in it are already available in ES2017.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.multiplyNumbers = void 0;
function timeout(n) {
return new Promise(res => setTimeout(res, n));
}
async function multiplyNumbers(a, b) {
await timeout(500);
return a * b;
}
exports.multiplyNumbers = multiplyNumbers;
(async () => {
console.log(await multiplyNumbers(3, 4));
})();
Static type-checking
I recommends that you use VSCode for text editor because it supports Typescript, unless you already have a favorite one. Most popular editors can be used for Typescript.
Create a new basics.ts
file, and let's start with initiating a variable:
let x = "hello world"
reassignment is fine, but you would get an error if you try to change type.
x = "hello mars";
x = 42; // π¨ Error: Type 'number' is not assignable to type 'string'.
Sometimes we need to declare a variable without initializing it, but have a type in mind:
let abc: number;
abc = 41;
abc = "abc"; // π¨ ERROR Type '"abc"' is not assignable to type 'number'.
abc(); // π¨ This expression is not callable. Type 'Number' has no call signatures.
Running that last sample with TypeScript will give us an error message before we run the code in the first place.
Array
To specify the type of an array like [1, 2, 3] , you can use the syntax number[] ; this syntax works for any type. You may also see this written as Array<number>
, which means the same thing.
let arr: number[] = [];
arr.push(33);
arr.push("abc"); // π¨ ERROR: Argument of type '"abc"' is not assignable to parameter of type 'number'.
You can even define a tuple, which has a fixed length:
let bb: [number, string, string, number] = [
123,
"Fake Street",
"Nowhere, USA",
10110
];
bb = [1, 2, 3]; // π¨ ERROR: Type 'number' is not assignable to type 'string'.
Objects
Apart from primitives, the most common sort of type you'll encounter is an object type. This refers to any JavaScript value with properties (which is almost all of them). Object types can be expressed using {}
and property names:
let cc: { houseNumber: number; streetName: string };
cc = {
streetName: "Fake Street",
houseNumber: 123
};
cc = {
houseNumber: 33
};
/**
* π¨ Property 'streetName'
* π¨ is missing in type '{ houseNumber: number; }'
* π¨ but required in type '{ houseNumber: number; streetName: string; }'.
*/
Youc an the streetName
property to be optional by using optional operator ?
.
let dd: { houseNumber: number; streetName?: string };
dd = {
houseNumber: 33
};
if we want to re-use this type, you can create an interface:
interface Address {
houseNumber: number;
streetName?: string;
}
// and refer to it by name
let ee: Address = { houseNumber: 33 };
Union
TypeScript's type system allows you to build new types out of existing ones using a large variety of operators. For example, sometimes we have a type that can be one of several things:
export interface HasPhoneNumber {
name: string;
phone: number;
}
export interface HasEmail {
name: string;
email: string;
}
let contactInfo: HasEmail | HasPhoneNumber =
Math.random() > 0.5
? {
// we can assign it to a HasPhoneNumber
name: "Mike",
phone: 3215551212
}
: {
// or a HasEmail
name: "Mike",
email: "mike@example.com"
};
contactInfo.name; // NOTE: we can only access the .name property (the stuff HasPhoneNumber and HasEmail have in common)
Or a type that is intersection of types
let otherContactInfo: HasEmail & HasPhoneNumber = {
// we _must_ initialize it to a shape that's asssignable to HasEmail _and_ HasPhoneNumber
name: "Mike",
email: "mike@example.com",
phone: 3215551212
};
otherContactInfo.name; // NOTE: we can access anything on _either_ type
otherContactInfo.email;
otherContactInfo.phone;