Introduction to TypeScript - what you have to know
Let's introduce TypeScript with some feature examples and configurations: how to compile TS files to JS files, work with classes / types / union types, functions, inheritance, and interfaces.
Compilation using Gulp
Gulp is a task runner available as an npm package. It provides a plugin to handle the TypeScript compilation. The only thing to do is to configure a task using gulp with gulp-typescript.
To download the gulp packages, you have to install Node.js (https://nodejs.org/) to get access to the npm packages:
Install Gulp using the following command line:
npm install gulp
Install Gulp-Typescript using the following command lines:
npm install gulp-typescript
To configure the Gulp task, just provide a JS file named gulpfile.js containing the task description.
Import Gulp and Gulp-TypeScript:
var gulp = require("gulp");
var ts = require("gulp-typescript");
Define the default task to transcompile your TS files:
gulp.task('default', function() { // Default task
var result = gulp.src([ // Sources
"myScript1.ts",
"myScript2.ts",
// Other files here
])
.pipe(ts({ // Trans-compile
out: "outputFile.js" // Merge into one output file
}));
return result.js.pipe(gulp.dest("./")); // output file desti nation
});
Once the default task lists all the TS files to transcompile, just call Gulp using the following command line:
gulp
Working with typed variables
Working with TypeScript is really similar to JS as the typing system is optional. Nevertheless, the common types in TS are as follows:
String
Number
Boolean
Any
Void
Enum
Array
With JS, you should write the following:
var myVar = 1.0;// or
var myVar = "hello !";
Here, you can write exactly the same with TS. The TS compiler will process the type inference and guess the variable type for you:
var myVar = 1.0; // Which is a number
// or
var myVar = "hello !"; // Which is a string
To specify the type of a variable with TS, type the following command:
var myVar: type = value;
Then, with the previous example, add the following code:
var myVar: number = 1.0;
// or
var myVar: string = "hello !";
// etc.
However, it's forbidden to assign a new value with a different type even if you don't mention the type as follows:
var myVar = 1.0; // Now, myVar is a number
// and
myVar = "hello !"; // Forbidden, "hello" is a string and not a number
To get the JS flexibility with variables, let's introduce the any type. The any type allows developers to create variables without any static type. The following is an example:
var myVar: any = 1.0; // Is a number but can be anything else
myVar = "Hello !"; // Allowed, myVar's type is "any"
The following is the screenshot of the types.ts file:
Let's introduce some specific types. It's the occasion to introduce the generics using TypeScript and enumerated types. The usage of numbers, Booleans, and strings is the same in TypeScript and JavaScript. So, no need to learn more.
Enumerated types
Working with enumerated types (enum) is like working with numbers. The syntax is as follows:
Access to an enumerated type in both the languages is as follows:
var myVar: FileAccess = FileAccess.Read; // Equivalent to 0
Array
Defining an array with TS is also similar to JS. The following is an example:
// In both languages
var myArray = [];
// or
var myArray = new Array();
With TS, array is a generic class. Then, you can specify the item's type contained in the array as follows:
var myArray = new Array<number>();
Note
Note: With TS, typing new Array() is equivalent to new Array<any>().
You can now access the common functions as follows:
var myArray = new Array<any>();
myArray.push("Hello !");
myArray.push("1");
myArray.splice(0, 1);
console.log(myArray); // "[1]"
Working with classes and interfaces
Classes and interfaces allow you to build types just as the Array class does. Once you create a class, you can create instances using the keyword new, which creates an object in the memory.
The following is an example:
var myArray = new Array<any>(); // Creates a new instance
Creating a class
The syntax in TS to define a class is as follows:
class Writer {
constructor() {
// initialize some things here
}
}
This generates the following in JS:
var Writer = (function () {
function Writer() {
}
return Writer;
})();
In both languages, you can create an instance of Writer:
var myInstance = new Writer();
You can also use modules that work as namespaces:
module MY_MODULE {
class Writer {
...
}
}
Access:
var writer = new MY_MODULE.Writer(...);
Creating class members
With JS and the conventions, you can write the following:
function Writer() {
this.myPublicMember = 0.0; // A public member
this._myPrivateMember = 1.0; // A member used as private
}
With TS, you can explicitly specify the access specifier of a member (public, private, and protected), which has been explained as follows:
Public: Any block of code can access the member to read and write
Private: Only this can access this member to read and write
Protected: External blocks of code cannot access the member; only this and specializers (inheritance) can access this member to read and write
Let's experiment using the Writer class:
// declare class
class Writer {
// Union types. Can be a "string" or
// an array of strings "Array<string>"
public message: string|string[];
private _privateMessage: string = "private message";
protected _protectedMessage: string;
// Constructor. Called by the "new" keyword
constructor(message: string|string[]) {
this.message = message;
this._protectedMessage = "Protected message !"; // Allowed
}
// A public function accessible from everywhere.
// Returns nothing. Then, its return type is "void".
public write(): void {
console.log(this.message); // Allowed
console.log(this._privateMessage); // Allowed
console.log(this._protectedMessage); // Allowed
}
}
var writer = new Writer("My Public Message !");
console.log(writer.message); // Allowed
console.log(writer._privateMessage); // Not allowed
console.log(writer._protectedMessage); // Not allowed
Working with inheritance
Let's create a new class that specializes the Writer class. The specialized classes can access all the public and protected members of the base class thanks to the inheritance. The extends keyword represents the inheritance.
Let's create a new class named BetterWriter that specializes (extends) the Writer class:
// The base class is "Writer"
class BetterWriter extends Writer {
constructor(message: string|string[]) {
// Call the base class' constructor
super(message);
}
// We can override the "write" function
public write(): void {
if (typeof this.message === "string") {
// Call the function "write" of the base class
// which is the "Writer" class
super.write();
}
else {
for (var i=0; i < this.message.length; i++) {
console.log(this.message[i]); // Allowed
console.log(this._privateMessage); // Not allowed
console.log(this._protectedMessage); // Allowed
}
}
}
}
Using interfaces
Interfaces are used to create contracts. It means that if a class implements an interface, the class must provide all the functions and members defined in the interface. If not, it doesn't respect the contract, and the compiler will output an error.
All the defined functions are public and all the defined members are public.
With Babylon.js, a good example is to use the IDisposable interface. It means that the users can call the method named dispose(). This function's job is to deactivate and/or deallocate the systems used.
The following is an example:
interface IWriter {
// The class "Writer" must have the "message" member
message: string|string[];
// The class "Writer" must provide the "resetMessages" function.
resetMessages(): void;
}
class Writer implements IWriter {
public message: string|string[];
...
constructor(...) {
...
}
...
// All functions declared in the interface are public.
public resetMessages(): void {
this.message = this._privateMessage = this._protectedMessage = "";
}
}