Skip to content
youhoc
  • Pages
    • Home
    • Modern App Guidelines
    • Linux
      • Day 1: Linux Distributions & Navigation
      • Day 2: User Management
      • Day 3: File Permission & Ownership
      • Day 4: Package Management
      • Day 5: Services Management
    • Javascript
      • JS The Weird Part
        • Execution Context
        • Types & Operators
        • Objects & Functions
        • Error Handling & Strict Mode
        • Typescript, ES6, Tra
      • Modern JS
        • JS in the Browser
        • Data Storage JSON
        • Modern JS
        • Advanced Objects & Methods
        • Webpack & Babel
        • Async
      • jQuery
        • In-depth Analysis of jQuery
      • React-ready JS
        • Arrow Function
        • Template Literals
        • Logical AND, OR, Ternary, Nullish Operators
        • Destructuring & Rest Operator
        • Array Method
        • Immutability and Spread Operator
        • Promises, Async/Await, Callback
    • PHP
      • gruntJS
      • composer
      • MySQL
    • Docker
      • Container Basics
      • Container Networking
      • Container Image
      • Container Volume & Persistent Data
      • Dockerfile
      • Docker Compose
      • Docker Registry
    • Node.js
      • Installing & Exploring
      • Loading Modules
      • npm - Get Command Input
      • Web Server
        • Express Web Server
        • Template Engine & MVC
      • Sequelize
        • Sequelize Transactions: Đảm Bảo Tính Toàn Vẹn Dữ Liệu
        • 7 loại Data Types phổ biến Trong Sequelize
        • Phân Trang (Pagination) Trong Express.js Với Sequelize/MySQL
      • File Upload with Multer, Express.js
      • Hướng dẫn Cơ bản về Rest API
      • Server-Side Validation Với Express-Validator
      • Authentication Trong REST API Với JWT
      • Node-cron Simple to Complex Setup with PM2
      • HTMx Form: Gửi request, nhận response, và swap DOM
    • ReactJS
      • React from Andrew
        • Summary from Next
        • 1. Basics
        • 2. React Components
        • 3. Webpack
        • 4. Styling with SCSS
        • 5. React Router
        • 6. React Hook
      • Modern React From The Beginning
        • Intro to JSX
        • Vite Build Tools
        • Basic Component Creation
        • Component State
        • Props & Component Composition
        • useState with Inputs & Form Submission
        • useEffect, useRef & Local Storage
        • Async / Await and Http Request in React
        • React Router: Declarative Mode
        • ContextAPI
        • React Router: Framework Mode
          • File-routing & HTML Layouts
          • Server-side Data Query
          • Links & Navigation
          • Loaders
    • Typescript
      • TypeScript cơ bản (phần 1)
      • TypeScript cơ bản (phần 2)
    • Payload CMS
    • Authentication

TypeScript cơ bản (phần 2)

Union Type, Object Type, Type Alias, và Generic
Trong , mỗi biến hay tham số chỉ nhận đúng một type. Nhưng thực tế không phải lúc nào cũng vậy — một hàm có thể nhận cả number lẫn string, một object có thể có cấu trúc phức tạp với nhiều field. Buổi này đi vào những cách TypeScript xử lý các tình huống đó.

Union Type — một biến, nhiều type

Khi một tham số có thể là nhiều kiểu khác nhau, dùng | để khai báo:
Lưu ý quan trọng: khi dùng union type, TypeScript yêu cầu bạn phải tự kiểm tra type bên trong hàm trước khi dùng — vì nó không biết lúc runtime biến đó đang là number hay string. Đây gọi là type narrowing (thu hẹp type), và typeof là cách phổ biến nhất để làm điều đó.

Object Type — khai báo cấu trúc của object

Thay vì chỉ khai báo object chung chung, TypeScript cho phép định nghĩa cụ thể object đó có những field nào và type của từng field:
Kiểu object như thế này xuất hiện thường xuyên khi truyền data giữa các hàm, hoặc khi nhận response từ API.
Và đây không phải custom type — đây chỉ là cách TypeScript mô tả cấu trúc của một object thông thường. Custom type thực sự đến ở phần tiếp theo.

Custom Type — đặt tên cho cấu trúc của bạn

Khi object type được dùng lại ở nhiều chỗ, viết inline mỗi lần sẽ rất rườm rà. TypeScript cho phép đặt tên cho cấu trúc đó bằng type:
Dấu ? sau tên field nghĩa là optional — bạn không bắt buộc phải cung cấp field đó khi tạo object. TypeScript sẽ hiểu type của age lúc này là number | undefined.
Sau khi định nghĩa, dùng như một type bình thường:
TypeScript kiểm tra chặt theo hai hướng — thiếu field bắt buộc hoặc thêm field không có trong type đều bị báo lỗi:
Điểm hay của custom type là dùng lại được ở bất kỳ đâu — tham số hàm, biến, mảng:

Type Alias — đặt tên cho type

Khi object type hoặc union type dài và dùng lại nhiều lần, TypeScript cho phép đặt tên cho chúng bằng từ khóa type:
Thay vì lặp lại { name: string; age: number; isActive: boolean } ở nhiều chỗ, bạn chỉ cần viết User. Type alias đặc biệt hữu dụng khi:
Một object type được dùng ở nhiều hàm khác nhau
Cần kết hợp nhiều type lại: type AdminUser = User & { permissions: string[] }
Union type dài và có ý nghĩa riêng: type Status = 'pending' | 'sent' | 'delivered'

Type trong Function — tham số, return type, và destructuring

Tham số và return type

Khai báo type cho tham số hàm thì đã quen. Ngoài ra nên khai báo luôn return type — type của giá trị hàm trả về — ngay sau dấu ngoặc đóng:
void nghĩa là hàm không trả về giá trị nào (hoặc trả về undefined). Nếu bạn vô tình thêm return someValue vào hàm void, TypeScript sẽ báo lỗi ngay.
Khi dùng custom type làm tham số, hàm sẽ trông gọn và rõ ràng hơn nhiều:

Destructuring trong tham số

Khi destructure object ngay trong tham số hàm, khai báo type theo cú pháp sau:
Trông hơi dài vì phải viết type inline. Nếu dùng lại nhiều lần, nên dùng type alias cho gọn:

Vì sao phải dùng as number? Và khi nào nên dùng as?

Nhìn lại ví dụ từ bài học:
Lý do phải dùng as number ở đây: hàm add nhận union type number | string, nên TypeScript suy ra giá trị trả về cũng là number | string. Nhưng printResult yêu cầu val: number — không chấp nhận string. Vì vậy phải dùng as number để nói với TypeScript: “Tin tôi, lúc runtime cái này chắc chắn là number.”
Cách làm này hoạt động được, nhưng trông có vẻ rối. Cách đúng hơn là khai báo kiểu trả về của hàm một cách rõ ràng:
Cú pháp : number sau dấu ngoặc đóng là khai báo return type của hàm. Khi làm vậy, TypeScript sẽ kiểm tra bên trong hàm có thực sự trả về number không — và bên ngoài hàm cũng biết chính xác type của giá trị trả về.
Nguyên tắc dùng as: chỉ dùng khi bạn biết chắc type lúc runtime mà TypeScript không tự suy ra được — chẳng hạn khi làm việc với DOM (as HTMLInputElement) hoặc khi nhận data từ ngoài vào. Không nên dùng as để “vá” lỗi type vì lười sửa hàm.

Interface — cách khác để định nghĩa object type

interface là cú pháp thay thế cho type khi định nghĩa cấu trúc object:

type vs interface — khác nhau ở đâu?

Về cơ bản, khi định nghĩa object type, cả hai đều làm được việc như nhau. Sự khác biệt nằm ở một số tình huống nâng cao hơn:
type
interface
Định nghĩa object
Union type (A | B)
Extend / kế thừa
type B = A & {...}
interface B extends A {...}
Merge declaration
✅ (khai báo 2 lần tự động merge)
Dùng với class
Được
Phổ biến hơn
There are no rows in this table
Trong thực tế: dùng interface khi định nghĩa cấu trúc object, đặc biệt nếu sau này có thể cần extend hoặc dùng với class. Dùng type khi cần union type, intersection, hoặc các type phức tạp hơn.
Nhiều codebase chọn một trong hai và dùng nhất quán — không cần trộn lẫn cả hai.

Generic Type — type linh hoạt theo ngữ cảnh

Generic là cách viết một type “chưa xác định” và để TypeScript điền vào sau. Dùng generic thay cho any khi bạn chưa biết trước type cụ thể là gì — nhưng vẫn muốn giữ type safety.
Generic có ích thật sự khi bạn viết hàm dùng chung cho nhiều type khác nhau, mà vẫn giữ được type safety. Vài ví dụ thực tế hay gặp:
Lấy phần tử đầu tiên của mảng:
Nếu không dùng generic, phải viết firstString, firstNumber… riêng từng cái, hoặc dùng any và mất type safety.
Wrap kết quả API — pattern rất phổ biến:
Một cái ApiResponse<T> dùng được cho mọi loại data, không cần viết lại cấu trúc { data, error, loading } nhiều lần.
Tóm lại: generic hữu ích khi bạn có logic hoặc cấu trúc lặp lại y chang nhưng type bên trong thay đổi. Nếu chỉ dùng một type duy nhất, không cần generic — khai báo thẳng type đó là đủ.
Khác với any — nếu dùng any, TypeScript sẽ không báo lỗi ở dòng cuối, dẫn đến vỡ lúc runtime. Generic giữ nguyên type safety: TypeScript biết result3boolean và kiểm tra đúng.
Trong thực tế, TypeScript thường tự suy ra T mà không cần khai báo tường minh:
TypeScript nhìn vào giá trị bạn truyền vào và tự suy ra T là gì — không cần bạn nói thêm.
Trường hợp phải khai báo tường minh là khi TypeScript không có đủ thông tin để tự suy:
Hoặc khi gọi fetch — không có giá trị nào lúc compile để TypeScript tự suy:
Tóm lại: để TypeScript tự suy trừ khi nó báo lỗi hoặc suy ra unknown — lúc đó mới cần khai báo tường minh.
Generic cũng là cú pháp đằng sau Array<string>Promise<string> — đó là lý do chúng trông giống nhau:
Array<string>string[] hoàn toàn tương đương — chỉ là hai cú pháp khác nhau. Cú pháp string[] ngắn gọn hơn nên phổ biến hơn trong thực tế.
<string> là tham số type, nói cho TypeScript biết “bên trong cái này chứa gì”:
Array<string> → mảng chứa string
Promise<string> → Promise mà khi resolve sẽ trả về string

Promise và fetch — type cần chú ý

Promise là một generic type. Khi khai báo, bạn cho TypeScript biết hàm async này sẽ trả về gì sau khi resolve:
async function getUser(): Promise<User> {
const res = await fetch('/api/user');
const data = await res.json();
return data;
}
Tuy nhiên, fetch có một điểm cần lưu ý: res.json() luôn trả về Promise<any> — TypeScript không biết gì về cấu trúc của data trả về. Vì vậy cần ép kiểu thủ công:
// Cách 1: ép kiểu khi gọi .json()
const data = await res.json() as User;

// Cách 2: khai báo generic cho res.json() (ít dùng hơn)
const data: User = await res.json();
Một vài điều cần nhớ khi dùng fetch:
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);

if (!res.ok) {
throw new Error(`HTTP error: ${res.status}`);
}

return res.json() as Promise<User>;
}
fetch không tự throw lỗi khi server trả về 404 hay 500 — phải tự kiểm tra res.ok
TypeScript không thể xác minh data từ API có đúng cấu trúc User không — đó là trách nhiệm của bạn (hoặc dùng thư viện như Zod để validate)
Nếu cần an toàn hơn, khai báo return type là Promise<User | null> và xử lý cả trường hợp thất bại

Tóm tắt

Buổi này có khá nhiều concept mới, nhưng tóm lại:
Union type (|) cho phép một biến nhận nhiều type, kèm theo việc phải dùng typeof để thu hẹp type bên trong hàm
Object type nên khai báo cấu trúc cụ thể, không dùng object chung chung
Khai báo return type của hàm giúp tránh phải dùng as ở nơi khác
Type aliasinterface đều dùng để đặt tên cho object type — type linh hoạt hơn, interface phổ biến hơn khi làm việc với object và class
Generic (Array<T>, Promise<T>) là cách TypeScript xử lý các cấu trúc “chứa gì đó” — type bên trong được xác định khi dùng
fetch luôn trả về Promise<any> — phải tự ép kiểu và tự validate
Bước tiếp theo: Enum, Tuple, và cách TypeScript làm việc với Class — những thứ giúp mô hình hóa data phức tạp hơn trong ứng dụng thực tế.
Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.