Typescript dành cho JS developers

Bài viết này cung cấp một cái nhìn tổng quan ngắn gọn về TypeScript, tập trung vào hệ thống kiểu dữ liệu của nó.
Typescript dành cho JS developers
Typescript dành cho JS developers

Typescript là gì?

 

Một phiên bản Javascript++

TypeScript là một ngôn ngữ mã nguồn mở dựa trên JavaScript, một trong những ngôn ngữ được sử dụng nhiều nhất trên thế giới. Typescript mở rộng thêm Javascript bằng cách thêm vào một số static types.

Types cung cấp một phương thức tường minh hơn để mô tả các hình thái của object, diễn giải documentation tốt hơn, thông qua đó TypeScript có thể xác định rằng code của bạn đang hoạt động chính xác hay không.

Chỉ định Types là không bắt buộc trong TypeScript. 

 

Đáng tin cậy

Tất cả code JavaScript hợp lệ cũng sẽ hợp lệ với TypeScript. Typecript không bắt lỗi kiểu dữ liệu trong lúc chạy run-time. Điều đó có nghĩa là bạn có thể viết code không mắc lỗi kiểu dữ liệu, nhưng bạn có thể sẽ gặp lỗi lúc thực thi. Bạn phải hết sức cẩn thận với dữ liệu bên ngoài thu được thông qua các ajax request, hay từ các hệ thống files, storage (localStorage, sessionStorage...).

Code TypeScript được chuyển thành code JavaScript thông qua trình biên dịch TypeScript hoặc Babel. Các mã JavaScript đã được biên dịch này sẽ clean, đơn giản, chạy ở tất cả mọi nơi cần JavaScript: trình duyệt web, Node.JS hoặc trong các ứng dụng của bạn.

 

Có thể áp dụng từ từ

Việc áp dụng TypeScript không phải là thay đổi đột ngột. Bạn có thể bắt đầu bằng cách chú thích JavaScript hiện có bằng JSDoc, sau đó chuyển dần một vài files sang TypeScript. Cứ thực hiện mọi thứ một cách từ từ cho tới khi codebase của bạn được chuyển đổi hoàn toàn.

Với tính năng chỉ định kiểu dữ liệu của TypeScript, bạn không cần phải chú thích code của mình trừ khi bạn muốn an toàn hơn ^_^.

 

TypeScript dành cho JavaScript developers

TypeScript có mối quan hệ hiếm có với JavaScript. TypeScript cung cấp tất cả các tính năng sẵn có của JavaScript cùng với một lớp bổ sung: Hệ thống định dạng kiểu dữ liệu của TypeScript.

Ví dụ: JavaScript cung cấp các kiểu dữ liệu nguyên thủy như string, number và object, nhưng nó không thể kiểm tra xem bạn đã gán chúng một cách nhất quán hay chưa. TypeScript thì có.

Điều này có nghĩa là code JavaScript đang hoạt động hiện tại của bạn cũng là code TypeScript. Lợi ích chính của TypeScript là nó có thể làm nổi bật các behaviour không mong muốn trong code, từ đó giảm nguy cơ gây ra lỗi.

Bài viết này cung cấp một cái nhìn tổng quan ngắn gọn về TypeScript, tập trung vào hệ thống kiểu dữ liệu của nó.

 

Suy đoán kiểu (Types by Inference)

TypeScript sẽ tự động tạo ra các kiểu dữ liệu tương ứng cho bạn trong nhiều trường hợp. Ví dụ: khi bạn tạo một biến và gán nó cho một giá trị cụ thể, TypeScript sẽ sử dụng giá trị của biến làm kiểu dữ liệu của nó.

let helloWorld = "Hello World"

Lúc này kiểu dữ liệu của helloWorld sẽ là string. Bạn cũng có thể viết một cách tường minh hơn như sau:

let helloWorld: string = "Hello World"

Dựa trên cách hoạt động của JavaScript, TypeScript có thể xây dựng một hệ thống chấp nhận code JavaScript nhưng có thêm types (mình hay gọi đùa là có thai haha). Điều này cung cấp một hệ thống kiểu dữ liệu mà không cần thêm các ký tự bổ sung để tạo kiểu tường minh trong code hiện tại. Đó là cách TypeScript biết rằng helloWorld là một string trong ví dụ đầu tiên.

 

Chỉ định kiểu (Defining types)

TypeScript cung cấp một cú pháp mở rộng giúp bạn có thể chỉ định chính xác kiểu dữ liệu mà bạn mong muốn.

Ví dụ: để tạo một object với suy đoán kiểu tự động bao gồm namestringidnumber, bạn có thể viết như sau:

const user = {
  name: "Hayes",
  id: 0,
}

Một cách thức xịn xò hơn là khai báo một interface:

interface User {
  name: string
  id: number
}

Sau đó, bạn có thể khai báo một object JavaScript mới, đồng thời chỉ định kiểu dữ liệu cho object này:

const user: User = {
  name: "Hayes",
  id: 0,
}

Nếu bạn cung cấp một object không phù hợp với interface bạn chỉ định, TypeScript sẽ xuất ra một cảnh báo:

const user: User = {
  username: "Hayes",
  id: 0,
}

Type '{ username: string; id: number; }' is not assignable to type 'User'. Object literal may only specify known properties, and 'username' does not exist in type 'User'.

Bạn cũng có thể khai báo kiểu dữ liệu cho các class:

interface User {
  name: string
  id: number
}

class UserAccount {
  name: string
  id: number

  constructor(name: string, id: number) {
    this.name = name
    this.id = id
  }

  function getAdminUser(): User {
    //...
  }

  function deleteUser(user: User) {
    // ...
  }
}

const user: User = new UserAccount("Murphy", 1)

Javascript hỗ trợ một số kiểu dữ liệu nguyên thủy như: boolean, bigint, null, number, string, symbol, object, và undefined. Bạn hoàn toàn có thể sử dụng các kiểu dữ liệu này khi khai báo interface. Typescript cũng hỗ trợ thêm một số kiểu mới:

  • void: chấp nhận giá trị null hoặc undefined. Thường được dùng khi khai báo một function mà không trả về giá trị nào.
  • any: cái gì cũng được. Typescript sẽ không kiểm tra kiểu dữ liệu khi gặp loại này.
  • unknown: tương tự như any,  bạn có thể gán giá trị với bất cứ kiểu dữ liệu gì. Tuy nhiên, nó có vài điểm khác biệt.

Nếu như any cho phép thực hiện bất kỳ operation nào mà không kiểm tra kiểu dữ liệu thì uknown lại gần như không cho phép thực hiện operation nào.

let myValue: unknown

myValue.bar() // Error
myValue.toString() // Error
myValue[0] // Error

Chúng ta cần sử dụng type-checking để có thể thực hiện các operation trên unknown:

let myVariable: unknow

if (typeof value === 'number') {
  myVariable++
}

if (value instanceOf Xyz) {
  myVariable.foo()
}
  • never: cái kiểu này thì hơi bựa, nó đại diện cho kiểu dữ liệu có giá trị không bao giờ xảy ra =)) (nghe hơi lùng bùng phải không ^_^). 

Ví dụ: hàm dưới đây luôn trả về một Exception, nên chẳng khi nào nó trả ra giá trị gì (vì code chạy tới đây là dừng cmnr), do đó kiểu dữ liệu ở đây sẽ là never.

let showError = (message: string): never => {
  throw new Error(message)
}

 

Tự định nghĩa kiểu dữ liệu mới

Với TypeScript, bạn có thể tạo các kiểu phức tạp bằng cách kết hợp lại các kiểu đơn giản. Có hai cách phổ biến để làm như vậy đó là UnionsGenerics.

Unions

Bạn có thể chỉ định một type mới chấp nhận giá trị là một trong một số types xác định nào đó. 

Ở ví dụ sau, bạn chỉ định một kiểu dữ liệu boolean chỉ chấp nhận 1 trong 2 giá trị là true hoặc false.

type MyBool = true | false

Một vài ví dụ khác cho bạn dễ hình dung:

type WindowStates = "open" | "closed" | "minimized"

type LockStates = "locked" | "unlocked"

type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9

Unions cũng cung cấp một cách thức để xử lý các kiểu dữ liệu khác nhau. Ví dụ, bạn có thể khai báo một function nhận giá trị là một string hoặc mảng string:

function getLength(obj: string | string[]) {
  return obj.length
}

 

Generics

Generics cung cấp các variables cho types. Một ví dụ phổ biến là array. Một array không chỉ định generics có thể chứa bất kỳ thứ gì. Một array được chỉ định generics giúp mô tả các giá trị mà array đó chứa.

type StringArray = Array<string>

type NumberArray = Array<number>

type ObjectWithNameArray = Array<{ name: string }>

Bạn cũng có thể khai báo một kiểu của riêng bạn thông qua generics

interface User {
  nane: string
  id: number
}

// UserArray là một kiểu mới, có giá trị là một mảng các User.
type UserArray = Array<User>

interface Backpack<Type> {
  add: (obj: Type) => void
  get: () => Type
}

// Đoạn code này giúp Typescript biết rằng
// có một hằng số là `backpack`, và không cần quan tâm nó được khai báo ở đâu.
declare const backpack: Backpack<string>

// object là một string, bởi vì chúng ta đã khai báo nó ở interface trên là phần biến của Backpack.
const object = backpack.get()

// Vì biến backpack là một chuỗi, bạn không thể đưa một number vào hàm add().
backpack.add(23) //Argument of type 'number' is not assignable to parameter of type 'string'.

 

Hệ thống kiểu dữ liệu có cấu trúc (Structural Type System)

Một trong những nguyên tắc cốt lõi của TypeScript là kiểm tra kiểu dữ liệu bằng cách tập trung vào hình thái (shape) mà các giá trị (values) có. Điều này được gọi là "duck typing" hay "structural typing".

Trong một hệ thống kiểu dữ liệu có cấu trúc, nếu 2 object có hình thái giống nhau thì chúng được xem là cùng type.

Ở ví dụ bên dưới, biến point được xem là thuộc kiểu Point.

interface Point {
  x: number
  y: number
}

function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`)
}

// logs "12, 26"
const point = { x: 12, y: 26 }
logPoint(point)

Biến point tuy chưa bao giờ được khai báo là một kiểu Point nhưng TypeScript so sánh hình thái của point với hình thái của kiểu Point trong quá trình kiểm tra kiểu dữ liệu. Chúng có hình thái giống nhau, vì vậy code hợp lệ.

Việc so khớp hình thái chỉ yêu cầu một tập hợp các trường của object cần so sánh.

const point3 = { x: 12, y: 26, z: 89 }
logPoint(point3) // logs "12, 26"

const rect = { x: 33, y: 3, width: 30, height: 80 }
logPoint(rect) // logs "33, 3"

const color = { hex: "#187ABF" }
logPoint(color) //Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, y

Một ví dụ khác sử dụng class:

class VirtualPoint {
  x: number
  y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }
}

const newVPoint = new VirtualPoint(13, 56)
logPoint(newVPoint) // logs "13, 56"

Nếu object hoặc class có tất cả các property bắt buộc, TypeScript sẽ coi chúng là cùng loại, bất kể cách thức triển khai là khác nhau hay không.

Comments

Bài viết nổi bật

Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.

Mục lục

Related posts

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.

Tin mới nhất

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Trong phần này, chúng ta sẽ tìm hiểu một số khái niệm cơ bản nhất về AWS là gì và một số lợi ích khi sử dụng AWS.
Trở thành một software developer hiệu suất cao không phải là điều dễ dàng. Điều này đòi hỏi bạn phải có kỹ năng và kiến thức về lập trình, cũng như cách tiếp cận và giải quyết các vấn đề phức tạp. Tuy nhiên, nếu bạn có chút kiên nhẫn và sự nỗ lực, bạn hoàn toàn có thể trở thành một developer tài năng và thành công.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Thông tin được định nghĩa dưới dạng dữ liệu, kiến thức về thông tin, và trí tuệ về tri thức.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.