Giới thiệu TypeScript - Chương trình đầu tiên

TypeScript là một ngôn ngữ lập trình, tương tự như JavaScript nó triển khai từ đặc tả kỹ thuật ngôn ngữ kịch bản ECMAScript. TypeScript được Microsoft phát triển, nó kế thừa hầu hết những gì chúng ta đã biết về JavaScript (biến, hằn, vòng lặp ...), nhưng nó mở rộng thêm một số tính năng như khái báo biến với kiểu dữ liệu cụ thể, giao diện ... Mục đích để giảm thiểu lỗi ngay từ khi viết code và có một cấu trúc chặt chẽ hiện đại để đáp ứng tốt hơn cho các dự án lớn (như Angular)

Phần này coi như đã biết về JavaScript cơ bản, nên chỉ tìm hiểu những đặc điểm khác biệt của TypeScript so với JavaScript, còn những vấn đề khác kiến thức kế thừa từ JS.

TypeScript được viết trong các file phần mở rộng đặt là .ts, code viết bằng TypeScript sau đó được biên dịch sang JavaScript thông thường để chạy trên trình duyệt! Như vậy, ta dùng TypeScript để viết JavaScript
Công cụ:

Công cụ để biên dịch code viết bằng TypeScript là typescript, là là một gói của NodeJs, quản lý bởi npm nên bạn phải cài đặt đầy đủ các thành phần này

Cài đặt NodeJS và NPM (download NodeJS, xem thêm Cài NodeJS NPM và Grunt)

Để cài đặt TypeScript gõ lệnh tại terminate

# npm install -g typescript

Sau khi cài đặt, thì sử dụng công cụ này bằng gõ lệnh tsc với các tham số tương ứng. Ví dụ có file main.ts muốn biên dịch thành JavaScript main.js thì gõ:

# tsc main.ts

#Biên dịch nhiều file
# tsc *.ts

#Tự động dịch nếu nội dung file thay đổi
# tsc main.ts --watch

Ngoài ra có thể tạo một file tsconfig.json để cấu hình tsc cho từng thư mục dự án: tsconfig-json

IDE dùng để soạn thảo code TS bạn có thể dùng Visual Studio Code (miễn phí)

Tạo một dự án đầu tiên với VS Code

Tạo một thư mục dự án của bạn, dùng VS Code mở thử mục đó ra, tạo một file mã nguồn TS hello.ts với nội dung:

var fullName: string;                   //Khai báo biến với TS cho phép chỉ định kiểu cụ thể
console.log('Xin chào, ' + fullName);

Tạo file cấu hình cho TS trên VS Code có tên tsconfig.json với nội dụng file như sau:

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true,
        "target": "es5"
    },
}
typescript

Khi đã có file tsconfig.json thì lúc nào muốn biên dịch TS thành JS chỉ việc gõ CTRL+SHIFT+B, menu đổ ra chọn mục: tsc: build, nó sẽ dịch từ TypeScript sang JavaScript cho bạn. Nếu chọn tsc: watch sẽ tự động dịch mỗi khi file nguồn cập nhật.

Ở ví dụ trên, sau khi biên dịch thì từ hello.ts đã có thêm file hello.js với nội dung

var fullName;
console.log('Xin chào, ' + fullName);

Chạy thử file js có thể tích hợp vào trang HTML để kiểm tra, hoặc chạy với NodeJS từ dòng lệnh terminate gõ (Không tương tác với DOM HTML)

#node hello.js

Sau đây tìm hiểu một số đặc điểm của TypeScript

Khai báo biến với kiểu dữ liệu cụ thể

TypeScript cho phép khai báo biến bằng cách chỉ ra kiểu dữ liệu cụ thể của biến

var username: string;
//Hoặc khởi tạo luôn var username: string = "XTLAB";
//Hoặc kiểu dữ liệu bất kỳ username: any;

Có các kiểu dữ liệu nguyên thủy string, number, boolean cũng có thể chỉ định biến đó nhận kiểu dữ liệu bất kỳ với any, việc cho khai báo như vậy giúp phát hiện lỗi ngay từ khi code, ví dụ khai báo biến là string lại gán số sẽ có thông báo lỗi khi dịch.

Khai báo biến mảng với kiểu dữ liệu cụ thể

Sử dụng ký hiệu [] sau kiểu dữ liệu

var learning: string[];
learning = ["JavaScript", "PHP", "CSS"];
//Hoặc var learning: string[] =  ["JavaScript", "PHP", "CSS"];
var js : string = learning[0];

Cũng có thể dùng ký hiệu {} để tạo mảng có chỉ số phức tạp (đối tượng với thuộc tính) như JS

var learning = {first: 'HTML', second: 'CSS'};
console.log(learning['first']);
console.log(learning.second);

Interface - Giao diện lập trình

TypeScript cho khai báo cấu trúc của một loại dữ liệu gọi là giao diện interface, những đối tượng có các thuộc tính, dữ liệu giống với giao diện thì có thể sử dụng nó như giao diện đã biết.

//Khai báo một giao diện
interface Product {
    name: string,
    price: number
}
//Hàm nhận tham số có giao diện Product
function showProduct(product: Product) {
    console.log(product.name + ':' + product.price);
}

//Tạo ba đối tượng
var p1 = {name: 'Iphone', price: 500, os: 'IOS10'};
var p2 = {name: 'Iphone', price: 'Không biết'};
var p3 = {name: 'Iphone',  os: 'IOS10'};

showProduct(p1);   //Ok vì p1 có giao diện Product

showProduct(p2);  //Báo Lỗi vì p2 có price kiểu string => khác giao diện Product
showProduct(p3);  //Báo Lỗi vì p3 không có thuộc tính price => khác giao diện Product

Class - Lớp và tính kế thừa lớp

Xây dựng và sử dụng lớp trong TypeScript thì tương tự như trong JavaScript (xem Lớp trong JavaScript trước, chỉ có điều các thuộc tính, tham số phương thức phải khai báo với kiểu dữ liệu cụ thể). Ngoài ra với TypeScipt các phương thức, thuộc tính là chung, riêng, bảo vệ (giống C#) được khái báo với từ khóa public (mặc định), private (không truy cập được ngoài lớp), protected (giống private, nhưng lớp kế thừa truy cập được).

Ví dụ sau convert mã JavaScript khai báo 1 lớp trong ví dụ ở Lớp trong JavaScript, thành phù hợp với TypeScript, hãy để ý khác biệt tham số các phương thức và thuộc tính của lớp giữa 2 phiên bản

//Khai báo một lớp có tên Product
class Product {
    name: string;
    price: number;
    infomation: string;
    //Hàm khởi tạo
    constructor(name: string, price: number) {
        this.name = name;
        this.price = price;
        this.infomation = `${name} - ${price}`;
    }

    //Khai báo một phương thức
    checkStore(storeid:number) {
        console.log(this.name + ' in store ' + storeid);
    }

    //Hàm getter
    get info() {
        return this.infomation;
    }

    //Hàm setter
    set info(i) {
        this.infomation = i;
    }

    //Phương thức tĩnh
    static convertMoney(m:number) {
        console.log(m);
        return  m + ' đồng';
    }
}

//SỬ DỤNG LỚP

//Tạo một đối tượng từ lớp bằng new
let sanpham = new Product('Iphone', 1000);

//truy cập thuộc tính đối tượng sanpham.name
console.log(sanpham.name);

//gọi một phương thức của đối tượng
sanpham.checkStore(100);

//Gọi setter
sanpham.info = 'Thông tin sản phẩm ...';

//Gọi getter
console.log(sanpham.info);

//Gọi một hàm tĩnh
Product.convertMoney(100000);

Sự kế thừa cũng tương tự

class Computer extends Product {
    store: number;
    constructor(name: string, price: number, store: number) {
        super(name, price);
        this.store = store;
    }

    set info(i: string) {
        //super.info(i) - nếu muốn thi hành phương thức của lớp cha
        this.infomation = name + ':'+i;
    }

    totalInStore() {
        console.log('totalInStore');
    }
}


//Sử dụng
let sanpham2 = new Computer('Dell', 2000, 1);
console.log(sanpham2.name);
sanpham2.checkStore(200);

sanpham2.info = 'Thông tin sản phẩm ...';

console.log(sanpham2.info);
sanpham2.totalInStore();

Generic - trong TypeScript

Kỹ thuật sử dụng Generic là cách xây dựng các hàm, interface, lớp ... trên một kiểu dữ liệu chung chung tự ký hiệu như kiểu T, kiểu K ..., sau đó khi sử dụng hàm, lớp ... thì mới chỉ rõ kiểu T là gì (number, string ...), kiểu K cụ thể là gì. Generic phổ biến trong nhiều ngôn ngữ lập trình như C#, Java, Generic trong Dart ...

//Khai báo một hàm Generic, kiểu dữ liệu đại diện T nào đó
//Hàm tạo ra mảng kiểu T
function getArray<T>(item : T ) : T[] {
    var ar = new Array<T>();
    ar.push(item);
    return ar;
}

//Tạo ra mảng kiểu String
var arString = getArray<T>("Phần tử 1");
arString.push("Phần tử 2");
arString.push(1); //Lỗi vì chèn vào mảng phần tử số

//Tạo ra mảng kiểu number
var arNumber = getArray<T>(22);

Cách làm tương tự với Interface và Class

interface KeyPair<T, U> {
    key: T;
    value: U;
}
let kv1: KeyPair<number, string> = { key:1, value:"Steve" };
let kv2: KeyPair<number, number> = { key:1, value:12345 };

class KeyValuePair<T, U> {
    private  key: T;
    private  value: U;
    setKeyValue(key: T, val: U): void {
        this.key = key;
        this.val = val;
    }
}

Module và Namespace - trong TypeScript

Module hóa là cách tổ chức code thành các thành phần (chia thành các file .ts), các thành phần đó khi nào cần sử dụng thì nạp vào.

Module hóa không phải của riêng TypeScript mà nó là chuẩn của ECMAScript, trong một file .ts nào muốn các thành phần của nó có thể sử dụng lại ở file .ts khác thì dùng tới từ khóa export cho các thành phần đó (các thành phần có thể là biến, hằng, lớp, đối tượng ...)

export { name1, name2, …, nameN };

Khi nào cần dùng đến các thành phần export thì nạp vào bằng từ khóa import

import {name1, name2, …, nameN } from './namemodule';
//namemodule đường dẫn tới file .ts

Ví dụ ở file module1.ts khai báo

var var1:string = 'A';
function fun1() {
    console.log('f1');
}
class classExample {
    property1: string
}

export {var1, fun1, classExample};

Như vậy xuất module có 3 thành phần, lúc này ở file khác, nếu muốn sử dụng 3 thành phần này thì khai báo với import

//Nạp 1 thành phần
import {classExample} from './module1';
//Nạp nhiều thành phần thì liệt kê ra
import {classExample, var1} from './module1';

Bạn có thể nạp tất cả các thành phần

import * as M from './module1';
//Truy cập bằng M.var1, M.classExample ...

Namespace giống như C#, PHP ... để tránh xung đột về tên. Bạn có thể gộp các lớp, hàm ... vào một Namespace do bạn đặt ví dụ:

namespace Utility {
    export function log(msg: string) {
        console.log(msg);
    }
    export function error(msg: string) {
        console.error(msg);
    }
    export class ClassExam {
        name: string;
    }
}

// sử dụng: truy cập tới hàm, lớp phải chỉ ra namespace cụ thể
Utility.log('Call me');
Utility.error('maybe!');
var cls : Utility.ClassExam = new Utility.ClassExam();

//Export Namespace
export {Utility}

Khi sử dụng lại code, có thể import theo Namespace

import {Utility} from './module1';

Ví dụ, nạp module jQuery, cài đặt jQuery

#npm install @types/jquery --save-dev
import * as $ from "jquery";

Đăng ký nhận bài viết mới