cv.cm

Lập trình · Ngôn ngữ lập trình

Hướng dẫn học TypeScript cơ bản cho người mới

7 phút đọc

Trả lời nhanh

TypeScript là JavaScript có kiểu tĩnh. Bạn thêm chú thích kiểu như let count: number = 0, và trình biên dịch tsc kiểm tra chúng trước khi chuyển mã thành JavaScript thuần chạy được trên mọi trình duyệt hoặc Node.js. Các ý tưởng chính là kiểu cơ bản, interface và type alias cho hình dạng đối tượng, union type với narrowing, và generic để tái sử dụng mã. Kiểu bị xóa khi chạy.

Mở Định dạng JSON

TypeScript là gì và dùng để làm gì

TypeScript là ngôn ngữ mã nguồn mở do Microsoft phát triển, phát hành lần đầu năm 2012. Mọi chương trình JavaScript hợp lệ cũng là TypeScript hợp lệ, nên bạn có thể áp dụng dần từng tệp. Bộ kiểm tra kiểu bắt các lỗi như viết sai tên thuộc tính hoặc thiếu kiểm tra null ngay khi bạn gõ, và trình soạn thảo dùng kiểu để tự động hoàn thành và đổi tên an toàn. Nó được dùng rộng rãi cho ứng dụng front end lớn (Angular được viết bằng nó, còn React, Vue và Svelte hỗ trợ rất tốt), back end Node.js và thư viện.

Cài đặt và chạy Hello, world

Hãy cài Node.js trước, rồi cài trình biên dịch vào dự án bằng npm. tsc biên dịch tệp .ts thành .js.

const message: string = "Hello, world";
console.log(message);
npm install --save-dev typescript
npx tsc --init          # creates tsconfig.json
npx tsc hello.ts        # writes hello.js
node hello.js
# Hello, world

Các phiên bản Node.js, Deno và Bun gần đây cũng có thể chạy trực tiếp tệp .ts bằng cách bỏ chú thích kiểu. Chúng không kiểm tra kiểu, vì vậy vẫn hãy chạy npx tsc --noEmit để tìm lỗi kiểu.

Kiểu cơ bản và suy luận kiểu

Bạn hiếm khi cần viết mọi kiểu. Khi khởi tạo biến, TypeScript suy luận kiểu của nó: let n = 5 là number. Hãy chú thích tham số hàm và API công khai.

KiểuVí dụGhi chú
numberlet n: number = 3.5Số nguyên và số thực
stringlet s: string = "hi"Văn bản
booleanlet ok: boolean = truetrue hoặc false
Mảngnumber[] hoặc Array<number>Hai cách viết có cùng ý nghĩa
Tuple[string, number]Độ dài và vị trí cố định
unknownlet x: unknownGiá trị bất kỳ, phải kiểm tra trước khi dùng
anylet y: anyTắt kiểm tra; nên tránh
void / neverkiểu trả về của hàmKhông trả về gì / không bao giờ trả về
let count = 0;          // inferred as number
count = "ten";          // error: Type 'string' is not assignable to type 'number'
const names: string[] = ["Ann", "Bo"];
const pair: [string, number] = ["age", 31];

Dòng thứ hai là lỗi biên dịch được cố ý đưa vào để minh họa những gì bộ kiểm tra báo.

Hàm, điều khiển luồng và thu hẹp kiểu

Điều khiển luồng (if, for, while, switch) giống hệt JavaScript. Điều TypeScript bổ sung là narrowing (thu hẹp kiểu): sau một phép kiểm tra như typeof x === "string", trình biên dịch biết kiểu cụ thể hơn bên trong nhánh đó.

function greet(name: string, greeting = "Hello"): string {
  return `${greeting}, ${name}!`;
}
function formatId(id: string | number): string {
  if (typeof id === "number") {
    return id.toFixed(0).padStart(6, "0"); // id is number here
  }
  return id.toUpperCase();                 // id is string here
}
console.log(greet("Lin"));    // Hello, Lin!
console.log(formatId(42));    // 000042
console.log(formatId("ab7")); // AB7

string | number là union type: giá trị thuộc một trong các kiểu được liệt kê. Tham số tùy chọn được viết name?: string, và kiểu của chúng bao gồm undefined.

Interface và type alias

Mô tả hình dạng đối tượng bằng interface hoặc bí danh type. TypeScript kiểm tra cấu trúc, không phải tên: bất kỳ đối tượng nào có đúng thuộc tính đều phù hợp.

interface User {
  id: number;
  name: string;
  email?: string;          // optional
  readonly createdAt: Date;
}
type Role = "admin" | "editor" | "viewer"; // string literal union
function describe(user: User, role: Role): string {
  const contact = user.email ?? "no email";
  return `${user.name} (${role}, ${contact})`;
}
const ann: User = { id: 1, name: "Ann", createdAt: new Date() };
console.log(describe(ann, "editor")); // Ann (editor, no email)

Tính năng đặc trưng: generic và discriminated union

Generic cho phép một hàm hoặc kiểu làm việc với nhiều kiểu mà vẫn giữ chúng liên kết với nhau. Discriminated union là union của các kiểu đối tượng có chung một trường literal, nên switch trên trường đó thu hẹp kiểu, và trình biên dịch có thể kiểm tra rằng mọi trường hợp đều được xử lý.

function first<T>(items: T[]): T | undefined {
  return items[0];
}
const n = first([10, 20]);   // n: number | undefined
const s = first(["a", "b"]); // s: string | undefined
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rect":
      return shape.width * shape.height;
  }
}
console.log(n, s, area({ kind: "rect", width: 2, height: 3 })); // 10 a 6

Các utility type có sẵn tạo kiểu mới từ kiểu cũ: Partial<User> biến mọi thuộc tính thành tùy chọn, Pick<User, "id" | "name"> chỉ giữ một số thuộc tính, và Record<string, number> mô tả một từ điển.

Xử lý lỗi

Lỗi hoạt động như trong JavaScript, với throw và try/catch. Khi bật tùy chọn strict, giá trị bắt được có kiểu unknown, vì bất kỳ giá trị nào cũng có thể bị ném, nên hãy kiểm tra trước khi dùng.

function parseAge(text: string): number {
  const age = Number(text);
  if (!Number.isInteger(age) || age < 0) {
    throw new Error(`invalid age: ${text}`);
  }
  return age;
}
try {
  parseAge("abc");
} catch (err: unknown) {
  const message = err instanceof Error ? err.message : String(err);
  console.log(message); // invalid age: abc
}

Một chương trình nhỏ hoàn chỉnh: kho hàng có kiểu

interface Item {
  name: string;
  price: number;
  qty: number;
}
type Summary = { total: number; cheapest?: Item; outOfStock: string[] };
function summarize(items: Item[]): Summary {
  let total = 0;
  let cheapest: Item | undefined;
  const outOfStock: string[] = [];
  for (const item of items) {
    total += item.price * item.qty;
    if (item.qty === 0) outOfStock.push(item.name);
    if (!cheapest || item.price < cheapest.price) cheapest = item;
  }
  return { total, cheapest, outOfStock };
}
const result = summarize([
  { name: "pen", price: 1.5, qty: 10 },
  { name: "book", price: 12, qty: 2 },
  { name: "bag", price: 30, qty: 0 },
]);
console.log(result.total, result.cheapest?.name, result.outOfStock);
// 39 pen [ 'bag' ]

Hãy thử đổi qty: 10 thành qty: "10": trình biên dịch báo lỗi trước cả khi mã chạy.

Những lỗi thường gặp của người mới

  • Dùng any để làm im lỗi. Nên dùng unknown rồi thu hẹp kiểu.
  • Lạm dụng ép kiểu as, vốn bảo trình biên dịch tin bạn thay vì kiểm tra.
  • Để strict tắt trong tsconfig.json. Hãy bật nó ở dự án mới; nó kích hoạt kiểm tra null.
  • Viết những kiểu mà trình biên dịch đã tự suy luận được, chỉ thêm rối mà không thêm an toàn.

Học tiếp ở đâu

Hãy đọc TypeScript Handbook chính thức, và thử nghiệm trong TypeScript Playground trên typescriptlang.org, nơi hiển thị JavaScript đã biên dịch và lỗi cạnh nhau. Sau đó học các tùy chọn tsconfig.json, tệp khai báo kiểu cho thư viện (các gói @types/...) và TypeScript với framework bạn chọn.

Câu hỏi thường gặp

TypeScript có phải ngôn ngữ khác với JavaScript không?

Nó là tập mở rộng (superset) của JavaScript: thêm cú pháp kiểu và bộ kiểm tra, rồi biên dịch thành JavaScript thuần. Hành vi khi chạy giống JavaScript mà nó sinh ra.

Có nên học JavaScript trước TypeScript không?

Hãy học nền tảng JavaScript trước (biến, hàm, đối tượng, promise), vì TypeScript không thay đổi chúng. Sau đó thêm TypeScript; kiểu dễ học hơn khi bạn đã quen hành vi lúc chạy.

interface và type khác nhau thế nào?

Cả hai đều mô tả hình dạng đối tượng. Interface có thể được mở rộng và gộp lại bằng cách khai báo lại; type alias còn có thể đặt tên cho union, tuple và các kiểu không phải đối tượng. Với hình dạng đối tượng thông thường, cái nào cũng được.

TypeScript có làm mã của tôi chạy nhanh hơn không?

Không. Kiểu bị loại bỏ khi biên dịch, nên kết quả chạy với tốc độ như JavaScript viết tay. Lợi ích là ít lỗi hơn và công cụ tốt hơn.

Tất cả hướng dẫn lập trình