Giai đoạn 1: Nền tảng TypeScript - Làm chủ hệ thống kiểu

Chào mừng đến với Giai đoạn 1
Chào mừng bạn đến với giai đoạn đầu tiên trong hành trình học TypeScript! Nếu bạn đang chuyển từ JavaScript, bạn sắp thêm một công cụ mạnh mẽ vào quy trình phát triển của mình: kiểm tra kiểu tĩnh. Mục tiêu của giai đoạn này là giúp bạn làm quen với hệ thống kiểu của TypeScript và hiểu cách kiểu có thể ngăn chặn lỗi và cải thiện chất lượng code.
Đây không phải là một hướng dẫn "TypeScript là gì?". Thay vào đó, chúng ta sẽ tập trung vào các khái niệm thực tế về hệ thống kiểu mà bạn sẽ sử dụng hàng ngày khi xây dựng các ứng dụng thực tế.
Thời gian cam kết: 2 tuần, 1-2 giờ mỗi ngày Điều kiện tiên quyết: Kinh nghiệm JavaScript (các tính năng ES6+)
Bạn sẽ học được gì
Đến cuối Giai đoạn 1, bạn sẽ có thể:
✅ Thiết lập dự án TypeScript từ đầu
✅ Hiểu suy luận kiểu và chú thích kiểu
✅ Sử dụng interface và type alias hiệu quả
✅ Viết các hàm và kiểu generic
✅ Áp dụng union type và intersection type
✅ Sử dụng utility type (Partial, Pick, Omit, Record)
✅ Triển khai type guard và thu hẹp kiểu
✅ Cấu hình tùy chọn trình biên dịch TypeScript
✅ Debug lỗi kiểu hiệu quả
Thiết lập môi trường của bạn
1. Cài đặt Node.js và TypeScript
Cài đặt Node.js 22+ LTS:
- Tải xuống: Node.js Official Site
- Xác minh:
node --versionsẽ hiển thị v22.x.x hoặc cao hơn
Cài đặt TypeScript toàn cục (tùy chọn nhưng hữu ích):
npm install -g typescriptXác minh cài đặt:
tsc --versionSẽ hiển thị TypeScript 5.3.0 hoặc cao hơn.
2. Chọn Editor của bạn
VS Code (Khuyến nghị):
- Tải xuống: VS Code
- Hỗ trợ TypeScript tốt nhất được tích hợp sẵn
- IntelliSense và kiểm tra kiểu xuất sắc
Extension VS Code khuyến nghị:
- Error Lens - Thông báo lỗi inline
- Pretty TypeScript Errors - Định dạng lỗi đẹp hơn
- TypeScript Importer - Gợi ý auto-import
Các lựa chọn thay thế:
- WebStorm - IDE mạnh mẽ với hỗ trợ TypeScript xuất sắc
- Cursor - Editor tích hợp AI với TypeScript intelligence
3. Tạo dự án TypeScript đầu tiên
Khởi tạo dự án mới:
mkdir typescript-fundamentals
cd typescript-fundamentals
npm init -y
npm install --save-dev typescript @types/nodeTạo tsconfig.json:
npx tsc --initCập nhật tsconfig.json với cài đặt strict:
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Tạo file TypeScript đầu tiên (src/index.ts):
// src/index.ts
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("TypeScript"));Biên dịch và chạy:
npx tsc
node dist/index.jsTốt hơn: Sử dụng tsx cho phát triển (không cần biên dịch):
npm install --save-dev tsx
npx tsx src/index.tsLưu ý: Trong các dự án production, bạn thường sẽ sử dụng các build tool như Vite hoặc webpack để xử lý biên dịch TypeScript cùng với bundling, code splitting, và tối ưu hóa. Tìm hiểu thêm tại JavaScript Build Tools & Bundlers Explained.
Hiểu về hệ thống kiểu của TypeScript
TypeScript là kiểu dần dần: bạn có thể thêm kiểu từng bước. Kiểu chỉ tồn tại tại thời điểm biên dịch và được xóa bỏ trong quá trình biên dịch sang JavaScript.
Suy luận kiểu
TypeScript thường có thể suy luận kiểu tự động:
// Suy luận kiểu - không cần chú thích
let message = "Hello"; // Kiểu: string
let count = 42; // Kiểu: number
let isActive = true; // Kiểu: boolean
// TypeScript suy luận kiểu trả về
function add(a: number, b: number) {
return a + b; // Kiểu trả về được suy luận là number
}
// Mảng
let numbers = [1, 2, 3]; // Kiểu: number[]
let mixed = [1, "two", true]; // Kiểu: (string | number | boolean)[]Thực hành tốt: Để TypeScript suy luận kiểu khi chúng rõ ràng. Thêm chú thích khi chúng cải thiện sự rõ ràng.
Chú thích kiểu cơ bản
// Kiểu nguyên thủy
let username: string = "Alice";
let age: number = 30;
let isStudent: boolean = false;
let empty: null = null;
let notDefined: undefined = undefined;
// Mảng
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"]; // Cú pháp generic
// Tuple - mảng độ dài cố định với các kiểu cụ thể
let user: [string, number] = ["Alice", 30];
let rgb: [number, number, number] = [255, 0, 128];
// Enum
enum Status {
Pending = "PENDING",
Approved = "APPROVED",
Rejected = "REJECTED"
}
let orderStatus: Status = Status.Pending;Kiểu đối tượng
// Kiểu đối tượng inline
let user: { name: string; age: number } = {
name: "Alice",
age: 30
};
// Thuộc tính tùy chọn
let person: { name: string; age?: number } = {
name: "Bob"
// age là tùy chọn
};
// Thuộc tính readonly
let config: { readonly apiKey: string } = {
apiKey: "secret-key"
};
// config.apiKey = "new-key"; // Lỗi: Không thể gán cho 'apiKey'Interface vs Type Alias
Cả hai đều có thể định nghĩa hình dạng đối tượng, nhưng chúng có sự khác biệt nhỏ.
Interface
interface User {
id: number;
name: string;
email: string;
age?: number; // Tùy chọn
readonly createdAt: Date; // Readonly
}
// Interface có thể được mở rộng
interface Admin extends User {
role: string;
permissions: string[];
}
const admin: Admin = {
id: 1,
name: "Alice",
email: "[email protected]",
createdAt: new Date(),
role: "super-admin",
permissions: ["read", "write", "delete"]
};
// Interface có thể được hợp nhất (declaration merging)
interface User {
lastLogin?: Date;
}
// User giờ có thuộc tính lastLoginType Alias
type User = {
id: number;
name: string;
email: string;
};
// Có thể đại diện cho kiểu nguyên thủy, union, tuple
type ID = string | number;
type Point = [number, number];
type Status = "pending" | "approved" | "rejected";
// Type intersection
type Timestamped = {
createdAt: Date;
updatedAt: Date;
};
type TimestampedUser = User & Timestamped;Khi nào nên dùng cái nào?
- Sử dụng interface cho các hình dạng đối tượng, đặc biệt khi bạn có thể mở rộng chúng
- Sử dụng type alias cho union, tuple, kiểu nguyên thủy, hoặc các phép biến đổi kiểu phức tạp
- Cả hai đều hoạt động cho hầu hết các trường hợp—chọn một phong cách và giữ nhất quán
Hàm và kiểu hàm
Chữ ký hàm
// Hàm có tên với kiểu
function add(a: number, b: number): number {
return a + b;
}
// Hàm mũi tên
const multiply = (a: number, b: number): number => a * b;
// Tham số tùy chọn
function greet(name: string, greeting?: string): string {
return `${greeting || "Hello"}, ${name}!`;
}
// Tham số mặc định
function createUser(name: string, role: string = "user"): User {
return { id: Date.now(), name, email: `${name}@example.com` };
}
// Tham số rest
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}Biểu thức kiểu hàm
// Kiểu hàm
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
// Kiểu callback
function processData(
data: string[],
callback: (item: string) => void
): void {
data.forEach(callback);
}
processData(["a", "b", "c"], (item) => console.log(item));Void và Never
// void - hàm không trả về gì
function log(message: string): void {
console.log(message);
// Không có câu lệnh return
}
// never - hàm không bao giờ trả về (throw hoặc vòng lặp vô hạn)
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// ...
}
}Generic
Generic cho phép bạn viết code có thể tái sử dụng, type-safe hoạt động với nhiều kiểu.
Hàm generic
// Hàm identity generic
function identity<T>(value: T): T {
return value;
}
// Sử dụng - TypeScript suy luận kiểu
let num = identity(42); // T là number
let str = identity("hello"); // T là string
// Tham số kiểu tường minh
let bool = identity<boolean>(true);
// Generic với ràng buộc
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // Kiểu: string
const age = getProperty(user, "age"); // Kiểu: number
// const invalid = getProperty(user, "email"); // Lỗi: "email" không có trong UserInterface generic
// Interface generic
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Sử dụng
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "Alice", email: "[email protected]" },
status: 200,
message: "Success"
};
const usersResponse: ApiResponse<User[]> = {
data: [
{ id: 1, name: "Alice", email: "[email protected]" },
{ id: 2, name: "Bob", email: "[email protected]" }
],
status: 200,
message: "Success"
};
// Generic với kiểu mặc định
interface Container<T = string> {
value: T;
}
const stringContainer: Container = { value: "hello" }; // T mặc định là string
const numberContainer: Container<number> = { value: 42 };Lớp generic
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
// Sử dụng
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2
const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");Union type và Intersection type
Union type (HOẶC)
// Union type - giá trị có thể là một trong nhiều kiểu
type ID = string | number;
function printId(id: ID): void {
console.log(`ID: ${id}`);
}
printId(123); // OK
printId("abc-123"); // OK
// Literal union type
type Status = "pending" | "approved" | "rejected";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function setStatus(status: Status): void {
// status chỉ có thể là một trong ba giá trị
}
setStatus("pending"); // OK
// setStatus("unknown"); // Lỗi
// Union với các kiểu khác nhau
type StringOrNumber = string | number;
type Result = { success: true; data: string } | { success: false; error: string };Intersection type (VÀ)
// Intersection type - kết hợp nhiều kiểu
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: string;
department: string;
};
type EmployeePerson = Person & Employee;
const employee: EmployeePerson = {
name: "Alice",
age: 30,
employeeId: "EMP001",
department: "Engineering"
};
// Ví dụ thực tế
type Timestamped = {
createdAt: Date;
updatedAt: Date;
};
type AuditLog = {
userId: string;
action: string;
};
type TimestampedLog = AuditLog & Timestamped;Type guard và thu hẹp kiểu
TypeScript có thể thu hẹp kiểu dựa trên các kiểm tra bạn thực hiện.
Type guard với typeof
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
// TypeScript biết padding là number ở đây
return " ".repeat(padding) + value;
}
// TypeScript biết padding là string ở đây
return padding + value;
}Thu hẹp kiểu với tính đúng sai
function printName(name: string | null | undefined): void {
if (name) {
// TypeScript biết name là string ở đây
console.log(name.toUpperCase());
} else {
console.log("No name provided");
}
}Type guard với instanceof
function processDate(date: Date | string): string {
if (date instanceof Date) {
// TypeScript biết date là Date ở đây
return date.toISOString();
}
// TypeScript biết date là string ở đây
return new Date(date).toISOString();
}Thu hẹp kiểu với toán tử in
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird): void {
if ("swim" in animal) {
// TypeScript biết animal là Fish ở đây
animal.swim();
} else {
// TypeScript biết animal là Bird ở đây
animal.fly();
}
}Type guard tùy chỉnh
// Type predicate
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processValue(value: unknown): void {
if (isString(value)) {
// TypeScript biết value là string ở đây
console.log(value.toUpperCase());
}
}
// Discriminated union
type Success = { status: "success"; data: string };
type Error = { status: "error"; message: string };
type Result = Success | Error;
function handleResult(result: Result): void {
if (result.status === "success") {
// TypeScript biết result là Success ở đây
console.log(result.data);
} else {
// TypeScript biết result là Error ở đây
console.log(result.message);
}
}Utility type
TypeScript cung cấp các utility type được tích hợp sẵn cho các phép biến đổi kiểu phổ biến.
Partial
interface User {
id: number;
name: string;
email: string;
}
// Partial làm tất cả thuộc tính trở thành tùy chọn
type PartialUser = Partial<User>;
// Tương đương với:
// {
// id?: number;
// name?: string;
// email?: string;
// }
function updateUser(id: number, updates: Partial<User>): void {
// Có thể cập nhật bất kỳ tập con nào của thuộc tính User
}
updateUser(1, { name: "Alice" }); // OK
updateUser(1, { email: "[email protected]" }); // OKRequired
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
// Required làm tất cả thuộc tính trở thành bắt buộc
type RequiredConfig = Required<Config>;
// Tất cả thuộc tính giờ là bắt buộcReadonly
// Readonly làm tất cả thuộc tính trở thành readonly
type ReadonlyUser = Readonly<User>;
// Tương đương với:
// {
// readonly id: number;
// readonly name: string;
// readonly email: string;
// }
const user: ReadonlyUser = { id: 1, name: "Alice", email: "[email protected]" };
// user.name = "Bob"; // Lỗi: Không thể gán cho 'name'Pick
// Pick chọn các thuộc tính cụ thể
type UserPreview = Pick<User, "id" | "name">;
// Tương đương với:
// {
// id: number;
// name: string;
// }Omit
// Omit loại trừ các thuộc tính cụ thể
type UserWithoutEmail = Omit<User, "email">;
// Tương đương với:
// {
// id: number;
// name: string;
// }Record
// Record tạo kiểu đối tượng với key và value cụ thể
type UserRoles = Record<string, string[]>;
// Tương đương với: { [key: string]: string[] }
const roles: UserRoles = {
admin: ["read", "write", "delete"],
user: ["read"]
};
// Record với key cụ thể
type PageInfo = Record<"home" | "about" | "contact", { title: string; description: string }>;ReturnType
function createUser(name: string, email: string) {
return { id: Date.now(), name, email, createdAt: new Date() };
}
// Trích xuất kiểu trả về từ hàm
type User = ReturnType<typeof createUser>;
// Kiểu: { id: number; name: string; email: string; createdAt: Date }Làm việc với Module
Xuất kiểu
// types.ts
export interface User {
id: number;
name: string;
email: string;
}
export type UserRole = "admin" | "user" | "guest";
export interface ApiResponse<T> {
data: T;
status: number;
}Nhập kiểu
// user-service.ts
import { User, UserRole, ApiResponse } from "./types";
function getUser(id: number): ApiResponse<User> {
// Triển khai
}
// Import chỉ kiểu (bị xóa tại runtime)
import type { User } from "./types";Cạm bẫy phổ biến và thực hành tốt
❌ Không dùng any
// ❌ Xấu - làm mất mục đích của TypeScript
function processData(data: any) {
return data.value; // Không có type safety
}
// ✅ Tốt - sử dụng kiểu phù hợp hoặc generic
function processData<T extends { value: string }>(data: T) {
return data.value;
}
// ✅ Hoặc sử dụng unknown cho kiểu thực sự không xác định
function processData(data: unknown) {
if (typeof data === "object" && data !== null && "value" in data) {
return (data as { value: string }).value;
}
}✅ Bật chế độ Strict
Luôn sử dụng "strict": true trong tsconfig.json. Nó bật:
strictNullChecks- Ngăn chặn lỗi null/undefinedstrictFunctionTypes- Kiểm tra tham số hàm nghiêm ngặt hơnstrictBindCallApply- bind/call/apply type-safenoImplicitAny- Yêu cầu kiểu tường minh- Và nhiều hơn nữa...
✅ Để TypeScript suy luận kiểu
// ❌ Chú thích quá mức
const numbers: number[] = [1, 2, 3];
const name: string = "Alice";
// ✅ Để TypeScript suy luận
const numbers = [1, 2, 3]; // Kiểu: number[]
const name = "Alice"; // Kiểu: string✅ Sử dụng Const Assertion
// Không có const assertion
const colors = ["red", "green", "blue"]; // Kiểu: string[]
// Với const assertion
const colors = ["red", "green", "blue"] as const; // Kiểu: readonly ["red", "green", "blue"]
// Hữu ích cho literal type
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
} as const;
// Kiểu: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }Cấu hình TypeScript
Các tùy chọn tsconfig.json thiết yếu
{
"compilerOptions": {
// Language và Environment
"target": "ES2023", // Phiên bản JavaScript đầu ra
"lib": ["ES2023"], // Các tính năng thư viện chuẩn có sẵn
// Module
"module": "NodeNext", // Hệ thống module
"moduleResolution": "NodeNext", // Cách module được phân giải
"resolveJsonModule": true, // Import file JSON
// Emit
"outDir": "./dist", // Thư mục đầu ra
"declaration": true, // Tạo file .d.ts
"sourceMap": true, // Tạo source map cho debug
// Type Checking (tất cả được bật bởi "strict": true)
"strict": true, // Bật tất cả kiểm tra strict
"noUncheckedIndexedAccess": true, // Thêm undefined vào index signature
"noImplicitOverride": true, // Yêu cầu từ khóa override
// Interop Constraint
"esModuleInterop": true, // Tương tác CommonJS/ESM tốt hơn
"forceConsistentCasingInFileNames": true, // Import phân biệt hoa thường
// Skip Lib Check
"skipLibCheck": true // Bỏ qua kiểm tra kiểu của file .d.ts
}
}Tóm tắt và điểm chính
Chúc mừng! Bạn đã hoàn thành Giai đoạn 1 của lộ trình TypeScript. Đây là những gì bạn đã làm chủ:
✅ Cơ bản về hệ thống kiểu: Kiểu nguyên thủy, đối tượng, mảng, tuple, enum
✅ Suy luận kiểu: Để TypeScript suy luận kiểu tự động
✅ Interface & Type: Định nghĩa hình dạng đối tượng và kiểu phức tạp
✅ Hàm: Chữ ký hàm và callback type-safe
✅ Generic: Viết code có thể tái sử dụng, type-safe
✅ Union & Intersection: Kết hợp kiểu theo cách mạnh mẽ
✅ Type Guard: Thu hẹp kiểu an toàn
✅ Utility Type: Biến đổi kiểu với helper tích hợp sẵn
✅ Hệ thống Module: Tổ chức code với import/export
✅ Cấu hình: Thiết lập dự án TypeScript đúng cách
Bước tiếp theo
Bây giờ bạn đã có nền tảng vững chắc về hệ thống kiểu TypeScript. Trong Giai đoạn 2, bạn sẽ áp dụng các khái niệm này để xây dựng ứng dụng React type-safe với React 19 và Next.js 15.
🎯 Tiếp tục đến Giai đoạn 2: Phát triển Frontend →
Tài nguyên bổ sung
- TypeScript Handbook
- TypeScript Deep Dive
- Type Challenges - Luyện tập kiểu nâng cao
Để tìm hiểu sâu hơn về các tính năng hệ thống kiểu nâng cao:
📘 Deep Dive: Kiểu TypeScript nâng cao →
Trước đó: Lộ trình học TypeScript Full-Stack → Tiếp theo: Giai đoạn 2: Phát triển Frontend →
Chúc bạn code vui vẻ! 🚀
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.