TypeScript là JavaScript có thêm một hệ thống kiểu tĩnh (static type system) đặt lên trên. Mọi chương trình JavaScript đều là cú pháp TypeScript hợp lệ; TypeScript bổ sung chú thích kiểu, một trình biên dịch kiểm tra chúng trước khi code chạy, và một bước build xóa chúng đi. Lúc chạy chỉ còn JavaScript, nên khác biệt nằm hoàn toàn ở những gì bạn phát hiện được trước khi phát hành.
Trong JavaScript, cùng hàm này chính là đoạn code trên bỏ đi type Product = ..., : Product[] và : number. Các kiểu bổ sung thông tin cho trình biên dịch và editor; chúng không thay đổi việc chương trình làm.
So sánh nhanh TypeScript và JavaScript
| JavaScript | TypeScript | |
|---|---|---|
| Hệ thống kiểu | Động: kiểu thuộc về giá trị và chỉ được biết lúc chạy | Tĩnh: kiểu được khai báo hoặc suy luận và kiểm tra lúc biên dịch |
| Khi nào lỗi kiểu xuất hiện | Khi dòng đó chạy (undefined, NaN, TypeError) | Ngay trong editor khi bạn gõ, và khi biên dịch |
| Chạy ở đâu | Trình duyệt, Node.js, Deno, Bun, chạy trực tiếp | Cùng những nơi đó, sau khi các kiểu bị xóa |
| Bước build | Không cần | tsc hoặc một bundler, hoặc runtime tự loại bỏ kiểu |
| Tệp | .js, .mjs, .cjs | .ts, .mts, .cts, .tsx, cùng tệp khai báo kiểu .d.ts |
| Tốc độ khi chạy | Mức cơ sở | Giống hệt: đầu ra là JavaScript |
| Hỗ trợ editor | Gợi ý code từ kiểu suy luận và typings của thư viện, có thể không đầy đủ | Gợi ý code, đổi tên và "find all references" dựa trên kiểu đã khai báo |
| Độ khó khi học | Thấp hơn | JavaScript cộng thêm hệ thống kiểu |
| Chuẩn | ECMAScript, do TC39 phát triển | Dự án mã nguồn mở của Microsoft, bám theo ECMAScript |
Cùng một đoạn code ở hai ngôn ngữ
Đây là một hàm viết bằng JavaScript. Không có gì trong đó cho biết user phải có dạng như thế nào:
function greeting(user) {
return `Hello, ${user.firstName} ${user.lastName}`;
}
greeting({ firstname: "Ada", lastName: "Lovelace" });
// "Hello, undefined Lovelace", no error anywhere
Phiên bản TypeScript khai báo hình dạng một lần, và lỗi gõ sai được báo trước khi code chạy:
interface User {
firstName: string;
lastName: string;
}
function greeting(user: User): string {
return `Hello, ${user.firstName} ${user.lastName}`;
}
greeting({ firstname: "Ada", lastName: "Lovelace" });
// error TS2561: Object literal may only specify known properties,
// but 'firstname' does not exist in type 'User'. Did you mean to write 'firstName'?
Chú thích kiểu là toàn bộ khác biệt về cú pháp. TypeScript cũng thêm vài kiểu khai báo riêng (interface, type, enum, generic như Array<string>, access modifier như private), nhưng các câu lệnh, toán tử và đối tượng có sẵn đều là của JavaScript.
Những lỗi TypeScript bắt được mà JavaScript thì không
JavaScript chuyển kiểu một cách lặng lẽ. Bug này rất hay gặp với giá trị lấy từ các ô nhập liệu của form, vốn luôn là chuỗi. Chạy thử để xem trình biên dịch nói gì:
index.ts(7,17): error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
Type 'string' is not assignable to type 'number'.
Trong JavaScript, đoạn này chạy được và in ra 010205, vì 0 + "10" là phép nối chuỗi. TypeScript từ chối biên dịch cho đến khi các chuỗi được chuyển đổi, ví dụ bằng fromForm.map(Number).
Nhóm lỗi lớn còn lại là những giá trị có thể không tồn tại. Array.prototype.find trả về undefined khi không tìm thấy phần tử nào khớp, và TypeScript buộc bạn phải xử lý trường hợp đó:
index.ts(8,13): error TS18048: 'user' is possibly 'undefined'.
Phiên bản JavaScript thuần bị crash lúc chạy với TypeError: Cannot read properties of undefined (reading 'name'). Cách sửa trong TypeScript là xử lý đúng trường hợp mà trình biên dịch đã chỉ ra:
Đầu ra:
GRACE
no user with id 3
Những gì TypeScript không bắt được: lỗi logic (một công thức sai vẫn có kiểu đúng), và mọi thứ liên quan đến dữ liệu đi vào chương trình lúc chạy. Một response API có kiểu User chỉ đúng khi máy chủ gửi nó đúng, vì các kiểu đã biến mất khi code chạy. Hãy kiểm tra những dữ liệu như vậy bằng code chạy lúc runtime.
Dùng thư viện JavaScript trong TypeScript
Mọi package npm đều dùng được từ TypeScript, vì đầu ra dù sao cũng là JavaScript. Kiểu cho một package đến từ một trong ba nơi:
- Package tự kèm tệp
.d.ts. Hầu hết package còn được duy trì tích cực đều làm vậy, và bạn không cần cài gì thêm. - Một package
@typesriêng từ dự án cộng đồng DefinitelyTyped:npm install --save-dev @types/lodashthêm kiểu cholodash. - Không có ở đâu cả. Khi đó, nếu bật
strict, chính câu lệnh import sẽ bị báo lỗi:
error TS7016: Could not find a declaration file for module 'lodash'. '/project/node_modules/lodash/lodash.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`
Cách sửa là cài package @types nếu có, hoặc tự mô tả module trong một tệp .d.ts; trang declaration file hướng dẫn cách làm.
Bước build
Trình duyệt và Node.js không kiểm tra kiểu, nên TypeScript cần một bước nằm giữa mã nguồn và code thực sự chạy. Có ba cách thiết lập phổ biến:
tscbiên dịch mọi thứ. Nó kiểm tra kiểu và ghi ra các tệp.js, thường vào thư mụcdist. Đơn giản, và là chuẩn cho các thư viện.- Bundler hoặc dev server xóa kiểu, còn
tsc --noEmitkiểm tra chúng. Vite và esbuild loại bỏ kiểu mà không kiểm tra, giúp tải lại nhanh; editor và một bước CI chạy bộ kiểm tra kiểu. - Runtime tự xóa kiểu. Các phiên bản Node.js hiện tại, Deno và Bun chạy trực tiếp tệp
.ts. Không runtime nào kiểm tra kiểu khi chạy, nêntsc --noEmit(hoặcdeno check) vẫn là cách để tìm lỗi kiểu.
JavaScript không cần bất cứ bước nào trong số này, và đó là lợi thế thực tế lớn nhất của nó với các script nhỏ. Chi phí của bước TypeScript chủ yếu là thiết lập, gồm một tsconfig.json và dev dependency typescript, cộng với thời gian biên dịch; trình biên dịch native của TypeScript 7 đã giảm thời gian đó khoảng mười lần trên các dự án lớn.
Độ khó khi học
Mọi điều bạn biết về JavaScript đều dùng được, vì phần chạy của TypeScript chính là JavaScript. Phần mới là hệ thống kiểu, và nó gồm nhiều lớp:
- Chú thích kiểu cho biến, tham số và giá trị trả về (
: string,: number[]). - Kiểu đối tượng với
interfacevàtype, thuộc tính tùy chọn, union nhưstring | number. - Thu hẹp kiểu (narrowing): kiểm tra một giá trị bằng
typeof,inhoặc===để trình biên dịch biết bạn đang ở trường hợp nào. - Generic, utility type như
Partial<T>vàPick<T, K>, và các kiểu nâng cao dành cho người viết thư viện.
Hai lớp đầu tiên đủ cho phần lớn code ứng dụng. Phần lớn kiểu được suy luận tự động, nên nhiều code TypeScript trông giống JavaScript, chỉ có chú thích kiểu ở chữ ký hàm.
Khi nào chọn TypeScript, khi nào chọn JavaScript
TypeScript có tốt hơn JavaScript không? Với code do nhiều người duy trì hoặc tồn tại nhiều năm, thường là có, và ngành phần mềm đã đi theo hướng đó: theo số người đóng góp hằng tháng mà GitHub thống kê, TypeScript đã vượt cả JavaScript và Python vào tháng 8 năm 2025 để trở thành ngôn ngữ được dùng nhiều nhất trên GitHub. Với các script nhỏ, JavaScript thuần thường là công cụ phù hợp hơn.
Chọn TypeScript khi:
- Có nhiều hơn một người làm việc trên code, hoặc code sẽ được duy trì trong nhiều tháng, nhiều năm.
- Codebase đủ lớn để bạn không thể nhớ hết chữ ký của mọi hàm.
- Bạn thường xuyên refactor: đổi tên một thuộc tính sẽ cập nhật mọi chỗ dùng, và trình biên dịch liệt kê những chỗ còn sót.
- Bạn phát hành một thư viện: các tệp
.d.tsmang lại gợi ý code và kiểm tra cho người dùng thư viện. - Framework yêu cầu nó. Ứng dụng Angular được viết bằng TypeScript, Next.js và Astro mặc định tạo dự án mới bằng TypeScript, và các template React, Vue, Svelte của Vite đều có bản TypeScript.
Chọn JavaScript khi:
- Chương trình là một script ngắn, một thử nghiệm dùng một lần, hoặc một đoạn code trong console của trình duyệt.
- Bạn mới học lập trình lần đầu và muốn tiếp cận ít khái niệm hơn cùng lúc.
- Không có bước build và bạn cũng không muốn có. Kể cả khi đó,
// @ts-checkkết hợp JSDoc vẫn cho bạn một phần kiểm tra trong tệp.jsthông thường.
Chuyển dự án JavaScript sang TypeScript
Việc chuyển đổi không cần diễn ra một lần. Trình biên dịch chấp nhận JavaScript bên cạnh TypeScript:
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
Với allowJs, các tệp .js được biên dịch và có thể import từ tệp .ts, cũng như ngược lại. Sau đó chuyển dần từng bước:
- Đổi tên một tệp từ
.jsthành.tsvà sửa các lỗi trình biên dịch báo trong tệp đó. - Bắt đầu từ các tệp lá (module tiện ích ít import), rồi tiến dần vào trong.
- Bật
checkJs, hoặc thêm// @ts-checkở đầu từng tệp.js, để kiểm tra kiểu các tệp chưa đổi tên.
Trong các tệp JavaScript được kiểm tra, chú thích JSDoc cung cấp kiểu:
// @ts-check
/**
* @param {number} price
* @param {number} qty
* @returns {number}
*/
function lineTotal(price, qty) {
return price * qty;
}
lineTotal("3", 2);
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
Một số nhóm dừng ở đó: tệp JavaScript có kiểu JSDoc, được tsc kiểm tra, và không có bước build cho chính code. Số khác chuyển hẳn sang .ts. Nếu bạn bật strict cho một dự án có sẵn, hãy chuẩn bị gặp nhiều lỗi lúc đầu; trang strict mode liệt kê những gì mỗi cờ kiểm tra để bạn bật lần lượt từng cờ.
Câu hỏi thường gặp
Điểm khác nhau chính giữa TypeScript và JavaScript là gì?
TypeScript bổ sung kiểu tĩnh cho JavaScript. Bạn mô tả mỗi giá trị là gì (name: string, items: Item[]), và trình biên dịch TypeScript báo lỗi trước khi code chạy. JavaScript không kiểm tra gì trước: một kiểu sai chỉ lộ ra khi dòng đó được thực thi, thường dưới dạng undefined hoặc một TypeError.
TypeScript có tốt hơn JavaScript không?
Với hầu hết dự án do nhiều người cùng duy trì, hoặc tồn tại lâu hơn vài tuần, câu trả lời là có: kiểu bắt được cả nhóm bug, giúp refactor an toàn và là nền tảng cho tính năng tự động gợi ý code trong editor. Với một script ngắn, một bản prototype nhanh hay một bài tập khi học, JavaScript thuần bắt đầu nhanh hơn và không cần thiết lập build.
TypeScript có chạy nhanh hơn JavaScript không?
Không, và cũng không chậm hơn. TypeScript được biên dịch sang JavaScript và các kiểu bị xóa đi, nên code thực sự chạy chính là JavaScript mà bạn tự viết tay. Chi phí thêm duy nhất là thời gian biên dịch trong lúc phát triển.
Nên học JavaScript hay TypeScript trước?
Hãy học JavaScript cơ bản trước, hoặc học song song với TypeScript. Mọi hành vi lúc chạy (biến, hàm, đối tượng, mảng, promise) đều là JavaScript, TypeScript chỉ mô tả chúng. Khi bạn đã viết được các chương trình JavaScript nhỏ, việc thêm kiểu chỉ là một bước ngắn.
Có thể dùng TypeScript và JavaScript trong cùng một dự án không?
Có. Đặt "allowJs": true trong tsconfig.json và trình biên dịch sẽ chấp nhận tệp .js bên cạnh tệp .ts. Thêm "checkJs": true (hoặc chú thích // @ts-check ở từng tệp) để kiểm tra kiểu cả các tệp JavaScript, dùng kiểu lấy từ chú thích JSDoc. Đây là cách thường dùng để chuyển dự án sang TypeScript từng tệp một.