MỘT GIỜ HỌC TYPESCRIPT

Khi bạn đọc bài này, nghĩa là bạn đang muốn học Typescript. Chắc cú luôn!

Nhưng không biết bạn đã biết gì về Javascript hay ES6 chưa? Nếu bạn chưa biết 2 thằng này thì thân ái yêu cầu bạn học ngay 2 thằng kia đi rồi quay lại bài viết của mình. Vì để bài viết của mình yêu cầu bạn phải biết JS và ES6 nhé.

Ok! Đọc tới đây thì mặc định bạn đã biết JS và ES6 nhé =]]]]. Chúng ta bắt đầu vào Typescript.

Lưu ý là tài liệu chính thống nhất là: typescriptlang.org. Bạn vào đây để được update những version mới nhất và chính chủ nhất!

Một số bài viết hay ho của mình khác!

#1 Trên tay nhanh Typescript

Trên trang chủ của Typescript có viết một câu: “TypeScript is a typed superset of JavaScript that compiles to plain JavaScript”. Câu này dịch sao thì bạn tự dịch nha.

Nhưng hiểu một cách nông thôn nhất, Typescript là một bản nâng cấp xịn xò hơn dành riêng cho Javascript:

Typescript = Javascript thuần + ES6 + ES7 + ES8 + typings + … <một số tính năng khác>

Nếu như bạn thường xuyên dùng Javascript (bao gồm luôn ES6) thì bạn cũng dễ nhận thấy một điều: “Javascript rất chuối”. Nó chuối vậy nhưng độ phổ biến của Javascript thì khỏi bàn. Ngày xưa JS đơn thuần chỉ xử lý với DOM. Sau đó Javascript tiến hóa lên các framework single page application như React, Vuejs, hay Angular để build front-end một cách chuyên nghiệp và xịn xò. Hay Javascript kiêm luôn nhiệm vụ làm server qua Nodejs.

Chốt lại là Javascript ngày càng ngày càng phổ biến. Nhưng nó vẫn chuối!

  • Javascript không có kiểu dữ liệu, nên đây là khó khăn lớn nhất khi code với Javascript trong việc kiểm soát, debug hay update gì đó.
  • Javascript có hổ trợ OOP (từ ES6 trở lên) nhưng yếu và chán lắm :(. Nó không chặt và dễ áp dụng như Java, .Net, …

Dù là nói Javascript khá chuối và đặc biệt hơn, Typescript nó khắc phục được một số điểm yếu kể trên. Nhưng có một điều Typescript không thể được dùng thay thế hoàn toàn cho Javascript được. Javascript luôn có một chổ đứng nhất định trong giới lập trình!

#2 Cài đặt Typescript

Có hai cách để cài đặt Typescript như sau:

  • Thông qua NPM (Node.js package manager)
  • Cài đặt thông qua TypeScript’s Visual Studio plugins. Vào đây để download.

Đối với NPM user, cài đặt Typescript như sau:

npm install -g typescript
#3 Hello world

Mở trình soạn thảo của bạn lên và tạo một file hello_world.ts như sau:

const sayHello = (word: string) => {
    return "Hello " + word;
}

const word = "World!";

document.body.textContent = sayHello(word);

Ghi nhớ file Typescript có đuôi mở rộng là .ts nhé. Bây giờ chúng ta sẽ tiến hành biên dịch file này.

Mở terminal lên, di chuyển tới thu mục chứa file .ts và gõ lệnh sau để tiến hành build file .ts trên thành code Javascript.

tsc hello_world.ts

Sau khi gõ lệnh và tiến hành biên dịch. Một file hello_world.js sẽ được sinh ra. Code Typescript được biên dịch thành code Javascript rồi. Quá hay phải không các bạn!

Sau đó bạn có thể test file Javascript mới được tạo ra trên trình duyệt nhé. Ví dụ:

<!DOCTYPE html>
<html>
    <head><title>TypeScript Greeter</title></head>
    <body>
        <script src="hello_world.js"></script>
    </body>
</html>

Bây giờ mình sẽ giới thiệu cho các bạn những thứ hay ho trong Typescript một cách cơ bản nhất!

#4 Type annotations

Chúng ta sẽ quay lại ví dụ trên để tìm hiểu về type trong Typescript. Bây giờ chúng ta sửa lại một chút.

const sayHello = (word: string) => {
    return "Hello " + word;
}

const word = [0, 1, 2];

document.body.textContent = sayHello(word);

Bây giờ bạn thử compile lại giống lúc nảy và xem chuyện gì xảy ra. Và đây là kết quả mình compile:

error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

Bạn hãy ngó lại đoạn code Typescript trên. Hàm sayHello rõ ràng là nhận vào một tham số word và tham số này có kiểu dữ liệu là string. Thế mà bên dưới bạn tryền vào một array. Typescript sẽ báo lỗi ngay chổ này. Như vậy chúng ta rút ra một vài điểm đáng lưu ý.

  • Typescript sẽ được compile ra code Javascript
  • Typescript có kiểu dữ liệu rõ ràng.
  • Typescript giúp chúng ta phát hiện lỗi sớm hơn (ngay quá trình compile đã phát hiện ra lỗi)

Typescript còn có khá là nhiều kiểu dữ liệu như: number, boolean, array, enum, … Bạn có thể xem thêm ở đây: https://www.typescriptlang.org/docs/handbook/basic-types.html

#5 Interfaces

Trước khi tìm hiểu interface trong Typescript là gì, thì chúng ta đi vào 1 ví dụ và xem code Javascript được biên dịch ra như thế nào.

interface Person {
    firstName: string;
    lastName: string;
    age: number;
}

const greeter = (person: Person) => {
    return `Hello ${person.firstName}`;
}

const user = { age: 25, firstName: "Le", lastName: "Thanh" };

document.body.textContent = greeter(user);

Ok. Bạn hãy đọc qua một lần ví dụ trên và bắt đầu biên dịch về Javascript. Kết quả biên dịch như sau:

var greeter = function (person) {
    return "Hello " + person.firstName;
};
var user = { age: 25, firstName: "Le", lastName: "Thanh" };
document.body.textContent = greeter(user);

Như bạn cũng thấy đoạn Javascript sau khi được biên dịch rất đơn giản, không hề có bất kì một dấu hiệu nào của “interface” cả. Vậy interface dùng để làm gì?

Câu trả lời là, interface như kiểu một “checker”. Nó sẽ đi kiểm tra tính đúng đắn về dữ liệu và kiểu dữ liệu của một object.

Giải thích cụ thể hơn: trong hàm greeter, sẽ nhận vào một object, object này “có kiểu” là interface Person. Như vậy, những properties của object được tryền vào hàm greeter sẽ phải thỏa mãn đúng tên (key), đúng kiểu dữ liệu của từng properties trong interface Person. Đọc tới đây nếu bạn chưa hiểu gì thì chắc lỗi mình. Vậy mình sẽ giải thích bằng ví dụ bên dưới cho từng trường hợp thỏa mãn hay không.

Ví dụ này, bạn chỉ cần việc thay biến const user = ... vào trong đoạn code Typescript trên và tiến hành biên dịch là được.

/* Những trường hợp biên dịch thành công */
const user  = { age: 25, firstName: "Le", lastName: "Thanh" };

// Trường hợp này vẫn thỏa mãn dù có thêm property "sex"
const user = { age: 25, firstName: "Le", lastName: "Thanh", sex: "male" };

/* Những trường hợp biên dịch bị lỗi (không thỏa điều kiện của interface) */
// Trường hợp này sai key
const user = { age: 25, first: "Le", last: "Thanh" };

// Trường hợp này sai kiểu dữ liệu
const user  = { age: "25", firstName: "Le", lastName: "Thanh" };

// Trường hợp này không đủ property
const user  = { firstName: "Le", lastName: "Thanh" };

Bạn PHẢI test những trường hợp trên thì mới thấy error message mà Typescript thông báo ra. Có như vậy mới hiểu được interface được.

Nhìn interface vậy thì hơi bị căng nhỉ. Vì trong trường hợp trên, param object truyền vào hàm phải có đủ toàn bộ các properties của interface. Vậy giả sử trong trường hợp bạn không muốn truyền vào đủ properties thì sao? Giờ chỉ muốn truyền vào firstName, lastName thôi, age không muốn truyền?

Thật ra interface cho phép các bạn làm điều đó. Bạn có thể set cho bất kì property nào của interface có required (bắt buộc) hay không.

interface Person {
    firstName: string;
    lastName: string;
    age?: number;
}

const greeter = (person: Person) => {
    return `Hello ${person.firstName}`;
}

// Param object không có property "age"
const user = { firstName: "Le", lastName: "Thanh" };

document.body.textContent = greeter(user);

Tóm lại là chỉ cần đặt dấu ? vào sau property name như dòng số 4 là được. Quá dễ phải không!

Interface trong Typescript còn khá nhiều thứ như: Readonly properties, Function Types, Indexable Types, Class Types, … Bạn có thể vào link này để xem chi tiết: https://www.typescriptlang.org/docs/handbook/interfaces.html

#6 Functions

Function thật ra đã rất quen thuộc trong Javascript hay ES6. Nhưng mình sẽ giới thiệu một số điểm mới hay dùng trong Typescript.

Arrow function kết hợp với type

Arrow function là một cú pháp được dùng rất nhiều trong ES6. Vậy khi kết với type trong Typesript thì nó sẽ như thế nào. Xem đoạn code bên dưới.

// Arrow function trong ES6
let sum = (x, y) => {
    return x + y;
}

// Arrow function trong Typescript
let sum = (x: number, y: number): number => {
    return x + y;
}

Nhờ vào việc có kiểu dữ liệu rõ ràng nên arrow function trong Typescript đã “chuẩn” hơn lúc nào hết!

Optional và Default Parameters

Trong Typescript còn hổ trợ chúng ta optional và default params như ví dụ bên dưới.

const buildName = (firstName: string, lastName = "Le", middleName?: string): string => {
    return `${firstName} ${middleName} ${lastName}`;
}

let result1 = buildName("Thanh");
let result2 = buildName("Thanh", undefined);
let result3 = buildName("Thanh", "Le", "Nhat");
let result4 = buildName("Thanh", "Le", "Nhat", "dev");

Ở ví dụ trên, chúng ta có 1 function là buildName, function này có 3 tham số đầu vào. Bạn hãy chú ý 3 tham số này.

  • firstName: tham số thứ nhất kiểu string
  • lastName: tham số thứ 2 kiểu string, đặc biệt là tham số này có giá trị default là “Le”, nghĩa là khi bạn không truyền giá trị cho tham số này (hoặc truyền null hay undefined) thì giá trị default sẽ được sử dụng. Ví dụ ở result1, result2.
  • midleName: tham số cuối cùng cũng là kiểu string. Đặc biệt đây là tham số optional, nghĩa là có truyền giá trị vào cho nó hay không đều được.

Ở result4 sẽ báo lỗi vì chúng ta đã truyền quá nhiều tham số vào.

Mình chỉ giới thiệu nhiêu đó thôi, còn nhiều thứ khác nữa. Bạn có thể xem ở đây: https://www.typescriptlang.org/docs/handbook/functions.html

#7 Classes

Khái niệm class (trong OOP) đã có từ phiên bản ES6. Bạn đã biết ES6 rồi thì bây giờ thử xem class bên Typescript nó cải tiến hơn như thế nào.

Xem ví dụ

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let thanhLe = new Employee("Le Nhat Thanh", "DEV");
console.log(thanhLe.getElevatorPitch());
console.log(thanhLe.name);
// Dòng trên bị lỗi do property "name" đang là private.

Mình ôn lại một chút kiến thức về OOP cho các bạn. Trong ví dụ trên, mình có một class Person có một constructor và thuộc tính name đang là protected. Tiếp theo mình có class Employee kế thừa class Person (thông qua từ khóa extends). Class này có một thuộc tính private là department, có một public method là getElevatorPitch().

Thuộc tính name được kế thừa lại trong class Employee nên nó sẽ thành private trong scope của Employee. Vì thế dòng console.log(howard.name); sẽ bị lỗi vì thuộc tính đang là private.

Giải thích đơn giản vậy thôi. Thông qua ví dụ trên bạn có thấy OOP đã thực sự “hồi sinh” của class trong Typescript không? Trước đó ở phiên bản ES6, OOP làm gì được như vậy. Typescript đã cãi thiện rất nhiều. Cú pháp của OOP của Typescript có vẻ giống với Java và C#.

Để tìm hiểu thêm về class trong Typescript, bạn vào link bên dưới nha. Có rất nhiều thứ hay ho trong đó như readonly property, abstraction class, static property, và những thứ nâng cao khác: https://www.typescriptlang.org/docs/handbook/classes.html

#8 Generics

Tưởng chỉ có ở mấy ngôn ngữ OOP như Java, C#. À, C++ cũng có Generics nhưng được biết đến với cái tên là template. Và bây giờ, Typescript cũng có, chẳng thua kém ai. Vậy Generics là gì?

// Normal arrow function
let myIdentity = (arg: number): number => arg;
let myIdentity = (arg: string): string => arg;

// Generic function
let myIdentity = <T>(arg: T):T => arg;

// How to call generic function?
myIdentity<string>("lenhatthanh.com");
myIdentity<number>(1603);
myIdentity<string>(1603); // error here

Khi chúng ta xây dựng một function (sau này là class, component) như ví dụ trên, nhận vào tham số arg là number, return một number. Nhưng đến lúc nào đó chúng ta muốn nhận vào string và return string. Đương nhiên trong Javascript khi không có sự ràng buộc kiểu dữ liệu thì việc đó thực hiện khác dễ dàng. Nhưng đối với Typescript đã được ràng buộc kiểu dữ liệu nên Generic mới được sinh ra để tiện cho các bạn build một function (class, component) cùng cấu trúc, tính năng nhưng với nhiều kiểu dữ liệu khác nhau như ví dụ trên.

Bạn có thể tìm hiểu thêm về Generic ở link sau với đầy đủ các tính năng như Generic class, constant, v.v: https://www.typescriptlang.org/docs/handbook/generics.html

#9 Enums

Enums là một kiểu dữ liệu cực kì thông dụng và quen thuộc trong các ngôn ngữ khác, và bây giờ nó đã xuất hiện trong Typescript.

Kiểu Enums cho phép các lập trình viên define một set nhiều constants, việc này cực kì thuận tiện trong quá trình code. Typescript cung cấp cả enums số vào chuỗi. Chúng ta cùng xem ví dụ.

enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}

console.log(Direction.Up); // = 1
console.log(Direction.Down); // = 2
console.log(Direction.Left); // = 3
console.log(Direction.Right); // = 4

Ở ví dụ trên, nếu bạn không init giá trị cho Up = 1, thì giá trị default của nó sẽ là 0, và các giá trị phía sau sẽ tự động tăng lên 1.

Một số dạng enum khác!

// String enums
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

// Enums kết hợp
enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES"
}

Giới thiệu Enums tới đây thôi. Enums còn khá là nhiều thứ nữa. Khi code tới đâu, chúng ta có thể tra tới đó để xem. Tham khảo link: https://www.typescriptlang.org/docs/handbook/enums.html

#10 Union Types

Khi bạn muốn một biến nào đó có thể có nhiều kiểu dữ liệu khác nhau, bạn có thể nhớ tới Union.

let unionVar = (arg: string | number) => arg;

unionVar("lenhatthanh.com");
unionVar(100);

Nhìn vào ví dụ, bạn sẽ hiểu ngay Union! Param arg có thể nhận kiểu dữ liệu là string hoặc number, lưu ý là chỉ 1 trong 2 thôi nhé. Không nhận cả 2 được. Đơn giản vậy thôi, đó chính là Union.

Một ví dụ ứng dụng trong thực tế của Union.

/* Define một số kiểu dữ liệu về Network State */
type NetworkLoadingState = {
    state: "loading";
};

type NetworkFailedState = {
    state: "failed";
    code: number;
};

type NetworkSuccessState = {
    state: "success";
    response: {
        title: string;
        duration: number;
        summary: string;
    };
};

/* Define NetworkState type */
type NetworkState =
    | NetworkLoadingState
    | NetworkFailedState
    | NetworkSuccessState;

/* Sử dụng switch case để có thể detect được network state hiện tại là gì */
function networkStatus(state: NetworkState): string {
    switch (state.state) {
        case "loading":
            return "Downloading...";
        case "failed":
            return `Error ${state.code} downloading`;
        case "success":
            return `Downloaded ${state.response.title} - ${state.response.summary}`;
    }
}

Mình nghĩ tới đây thôi. Giới thiệu thôi mà. Nhiều quá thì hơi căng. Typescript thật ra cũng không có gì phức tạp nếu bạn đã biết Javascript và ES6. Thôi thì chúc các bạn may mắn nếu có cơ hội làm việc với Typescript.

Bài viết có vấn đề gì có thể để lại comment cho mình nhé. Rất mong nhận được sự góp ý của các bạn.

Tham khảo: https://www.typescriptlang.org/docs/home



8 Bình luận

Bạn thắc mắc điều gì?