TypeScript Cơ bản phần 2

Tiếp tục phần 1 (TypeScript Cơ bản phần 1) phần này mình xin chia sẻ tiếp một số kiến thức nữa về TypeScript

I, Interfaces

Trong TypeScript hỗ trợ cho người dùng khai báo với kiểu interface, Để đơn giản bạn hãy tưởng tượng interface giống như một cái “phôi” và từ cái “phôi” này bạn có thể tạo ra rất nhiều thứ khác nhau nhưng có chung những thuộc tính cơ bản mà cái “phôi” đó mang lại.

  1. Base
    function printLabel(labelledObj: {label: string}) {
      console.log(labelledObj.label);
    }
    
    var myObj = {size: 10, label: "Size 10 Object"};
    printLabel(myObj);

    Như ở ví dụ trên ta sẽ thấy  đầu vào của hàm printLabel là một object với key là label và kiểu dữ liệu ứng với key là string. Để ngắn gọn và dễ hiểu hơn ta sẽ viết lại như sau:

    interface LabelledValue {
      label: string;
    }
    
    function printLabel(labelledObj: LabelledValue) {
      console.log(labelledObj.label);
    }
    
    var myObj = {size: 10, label: "Size 10 Object"};
    printLabel(myObj);

    Các bạn sẽ thấy ở đây ta sẽ tạo ra một interface LabelledValue interface này được định nghĩa với một key là lable với kiểu là string.

    khi gọi hàm printLabel(myObj) trương trình sẽ so sánh xem đầu vào(myObj) có key là lable hay không. Có sẽ trả về là đúng và thực hiện hàm.

  2. Class Type
    Class cũng có thể dễ dàng sử dụng lại các thuộc tính của interface. Chúng ta sử dụng key implements cho việc kế thừa lại các interface

    interface ClockInterface {
        currentTime: Date;
        setTime(d: Date);
    }
    
    class Clock implements ClockInterface  {
        currentTime: Date;
        setTime(d: Date) {
            this.currentTime = d;
        }
        constructor(h: number, m: number) { }
    }

    Như ở trên chúng ta sẽ khai báo một interface là ClockInterface trong interface này có 1 param là curentTime và 1 function là setTime. Sau khi class Clock sử dụng ClockInterface đã định nghĩa lại function setTime và bổ sung thêm một function mới là constructor.

  3. Extending interface
    Các interface cung có thể sử dụng lại nhau. Ta hãy xem ví dụ.

    interface Shape {
        color: string;
    }
    
    interface Square extends Shape {
        sideLength: number;
    }

    Như ví dụ trên kết quả trả ra sẽ là interface Square sẽ có 2 param là color và sideLength

    Khi muốn sử dụng nhiều interface ta sẽ là như sau

    interface Shape {
        color: string;
    }
    
    interface PenStroke {
        penWidth: number;
    }
    
    interface Square extends Shape, PenStroke {
        sideLength: number;
    }

    Như ví dụ trên kết quả trả ra sẽ là interface Square sẽ có 3 param là color, sideLength và penWidth

Qua đây chúng ta rễ dàng nhận ra rằng việc sử dụng interface sẽ  giúp việc cấu trúc code một cách tốt hơn, code sẽ dễ học dễ hiểu hơn.

II, Modules

Khi làm việc với nhiều thành phần cùng chung một kiểu chức năng người lập trình hay nhóm chúng lại thành một nhóm để dễ sử dụng cũng như fixbug hay mở rộng. Trong TS việc sử dụng module cũng mang ý nghĩa như vậy. Sử dụng một ví dụ về việc validate string để chúng ta hiểu rõ hơn về module

interface StringValidator {
    isAcceptable(s: string): boolean;
}

var lettersRegexp = /^[A-Za-z]+$/;
var numberRegexp = /^[0-9]+$/;

class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}

class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}

Như ở trên chúng ta muốn validate string dưới dạng zipCode và LetterOnly. Thông thường người lập trình sẽ tạo ra 2 class như ví dụ trên, khi sử dụng ta sẽ dùng như sau:

var validatorsZipCode = new ZipCodeValidator();
var validatorsLetterOnly = new LettersOnlyValidator();
var string = "Hello"
validatorsLetterOnly.isAcceptable(string) // true
validatorsZipCode.isAcceptable(string) // false

Để làm cho code dễ hiểu hơn ta có thể sử dụng module để nhóm tất cả việc validate lại thành 1 “gói” là Validate

module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    var lettersRegexp = /^[A-Za-z]+$/;
    var numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

Khi sử dụng module việc sử dụng sẽ như sau:

var validatorsZipCode.isAcceptable = new Validation.ZipCodeValidator();
var validatorsLetterOnly.isAcceptable = new Validation.LettersOnlyValidator();
var string = "Hello"
validatorsLetterOnly.isAcceptable(string) // true
validatorsZipCode.isAcceptable(string) // false

Như ta thấy việc sử dụng module ở đây sẽ làm cho code dễ hiểu và gọn gàng hơn. Không nhưng thế với những xử lý phức tạp việc module hóa sẽ làm cho code của chúng ta dễ quản lý và sử dụng hơn.

Ở đây việc sử dụng export sẽ giúp việc gọi trực tiếp một phần tử trong module bằng cách module.XXXX Như ví dụ trên nếu bạn gọi Validation.lettersRegexp sẽ báo lỗi.

III, Declaration Merging

  1. Merging Interfaces
    Nói một cách đơn giản khi bạn khai báo nhiều interface cùng tên TS sẽ hiểu là 1 interface với các thuộc tính là cộng gộp với nhau. Giải thích hơi khó hiểu nên chúng ta sẽ xem ví dụ sau:

    interface Box {
        height: number;
        width: number;
    }
    
    interface Box {
        scale: number;
    }
    

    việc này sẽ tương đương với chúng ta khai báo một interface như sau:

    interface Box {
        height: number;
        width: number;
        scale: number;
    }
    

    Chú ý: khi khai báo kiểu này nếu có sự trùng lặp tên biến trong interface sẽ sảy ra lỗi khi biên dịch.

    interface Box {
        height: number;
        width: number;
    }
    
    interface Box {
        scale: number;
        width: number; // lỗi Duplicate identifier 'width'.
    }
    

    Đối với function trong interface thì có thể trùng tên nhưng giá trị đầu vào không được giống nhau.

    interface Document {
        createElement(tagName: any): Element;
    }
    interface Document {
        createElement(tagName: string): HTMLElement;
    }
    interface Document {
        createElement(tagName: "div"): HTMLDivElement; 
        createElement(tagName: "span"): HTMLSpanElement;
        createElement(tagName: "canvas"): HTMLCanvasElement;
    }
    

    Việc khai báo này tương đương với

    interface Document {
        createElement(tagName: "div"): HTMLDivElement; 
        createElement(tagName: "span"): HTMLSpanElement;
        createElement(tagName: "canvas"): HTMLCanvasElement;
        createElement(tagName: string): HTMLElement;
        createElement(tagName: any): Element;
    }
    
  2. Merging Modules
    Đối với Modules cũng tương tự interface. Nghĩa là các module cùng tên sẽ được gộp lại với nhau:

    module Animals {
        export class Zebra { }
    }
    
    module Animals {
        export interface Legged { numberOfLegs: number; }
        export class Dog { }
    }
    

    Việc này sẽ tương đương với:

    module Animals {
        export interface Legged { numberOfLegs: number; }
        
        export class Zebra { }
        export class Dog { }
    }
    

    Nhưng ở module các biến khai báo chỉ có ý nghĩa trong blog của nó. ví dụ:

    module Animal {
        var haveMuscles = true;
    
        export function animalsHaveMuscles() {
            return haveMuscles;
        }
    }
    
    module Animal {
        export function doAnimalsHaveMuscles() {
            return haveMuscles;  // error, haveMuscles is not visible here
        }
    }
    

    Ở đây xẽ sảy ra lỗi tại function doAnimalsHaveMuscles vì trong module Animal lúc này biến số haveMuscless không tồn tại.

  3. Merging modules with function, class, enums
    Việc này chúng ta có thể hiểu nôm na là cũng một tên ta có thể sử dụng vừa như là function, class, enums vừa như mà module. Ví du:

    function buildLabel(name: string): string {
        return buildLabel.prefix + name + buildLabel.suffix;
    }
    
    module buildLabel {
        export var suffix = "";
        export var prefix = "Hello, ";
    }
    
    buildLabel("Bob") // Hello, Bob
    buildLabel.prefix // Hello,
    

    Chú ý nếu khai báo module trước sẽ sảy ra lỗi khi biên dịch : A module declaration cannot be located prior to a class or function with which it is merged

Tới đây chúng ta đã hiểu được phần nào về TypeScript. Bài viết còn có nhiều thiếu sót mong mọi người góp ý để hoàn thiện hơn.

Add a Comment

Scroll Up