TypeScript vs JavaScript 2026: Khi nào nên (và không nên) dùng TypeScript
TypeScript không phải lúc nào cũng là lựa chọn đúng. Đây là decision framework dựa trên kinh nghiệm thực tế từ 10+ projects.
Năm 2020, tôi refactor một codebase JavaScript 50,000 dòng sang TypeScript.
Mất 3 tháng. Tìm được 47 bugs. Team phàn nàn về “quá nhiều type errors”.
Năm 2024, tôi build một MVP với JavaScript thuần. Ship trong 2 tuần. Sau 6 tháng, migrate sang TypeScript.
Đây là những gì tôi học được về khi nào nên (và không nên) dùng TypeScript.
TypeScript vs JavaScript: Không phải cuộc chiến
JavaScript:
- Dynamic typing
- Flexible, nhanh
- Ecosystem lớn
TypeScript:
- Static typing (compile-time)
- Safe, maintainable
- Superset của JavaScript
Key point: TypeScript = JavaScript + Types. Mọi JavaScript code đều là valid TypeScript (với any).
Khi nào NÊN dùng TypeScript?
Use Case 1: Large Codebase (> 10,000 dòng)
Vấn đề với JavaScript:
// user.js
function getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// order.js
const user = getUser(123);
console.log(user.name); // Runtime error: Cannot read property 'name' of Promise
Bug: getUser return Promise, không phải User object. Chỉ phát hiện khi chạy code.
TypeScript solution:
// user.ts
interface User {
id: number;
name: string;
email: string;
}
async function getUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// order.ts
const user = getUser(123);
console.log(user.name); // Compile error: Property 'name' does not exist on type 'Promise<User>'
// Fix:
const user = await getUser(123);
console.log(user.name); // OK
Benefit: Catch error trước khi deploy.
Use Case 2: Team > 5 người
Vấn đề với JavaScript:
// Dev A viết:
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Dev B dùng (3 tháng sau):
const total = calculateTotal([
{ name: 'Product A', cost: 100 } // Bug: field là 'cost', không phải 'price'
]);
console.log(total); // NaN
TypeScript solution:
interface Item {
name: string;
price: number;
}
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
const total = calculateTotal([
{ name: 'Product A', cost: 100 } // Compile error: Object literal may only specify known properties
]);
Benefit: Types là documentation. Dev B biết ngay phải dùng price.
Use Case 3: Refactoring lớn
Scenario: Đổi API response structure
JavaScript:
// Trước:
// API trả về: { user: { id, name } }
// Sau:
// API trả về: { data: { user: { id, name } } }
// Phải tìm và fix manually tất cả chỗ dùng
// Dễ miss → Bugs trong production
TypeScript:
interface ApiResponse {
data: {
user: User;
};
}
// Change interface → Compiler báo lỗi tất cả chỗ cần fix
// Không thể miss
Benefit: Safe refactoring.
Khi nào KHÔNG NÊN dùng TypeScript?
Use Case 1: Prototype/MVP
Scenario: Build MVP trong 2 tuần để validate idea
JavaScript:
// Viết nhanh, không care types
app.post('/api/orders', (req, res) => {
const order = req.body;
db.orders.insert(order);
res.json({ success: true });
});
TypeScript:
// Phải define types trước
interface Order {
userId: number;
items: OrderItem[];
total: number;
// ... 10 fields khác
}
interface OrderItem {
productId: number;
quantity: number;
price: number;
}
app.post('/api/orders', (req: Request, res: Response) => {
const order: Order = req.body; // Phải validate
db.orders.insert(order);
res.json({ success: true });
});
Overhead: Mất thời gian define types thay vì ship features.
Verdict: Dùng JavaScript cho MVP. Migrate sang TypeScript sau nếu product succeed.
Use Case 2: Script nhỏ, one-off tasks
Scenario: Script để migrate data, seed database
JavaScript:
// migrate.js
const users = require('./users.json');
users.forEach(user => {
db.users.insert(user);
});
TypeScript: Overkill. Phải setup tsconfig, compile, etc.
Verdict: JavaScript đủ.
Use Case 3: Team chưa biết TypeScript
Scenario: Team toàn junior devs, chưa ai biết TypeScript
Vấn đề:
- Learning curve
- Productivity giảm 30-50% trong 2-3 tháng đầu
- Frustration với type errors
Verdict: Đừng force TypeScript. Đào tạo trước, hoặc dùng JavaScript.
Migration Strategy: JavaScript → TypeScript
Đừng migrate toàn bộ codebase một lúc.
Step 1: Setup TypeScript (allow JS)
// tsconfig.json
{
"compilerOptions": {
"allowJs": true, // Cho phép .js files
"checkJs": false, // Không check .js files
"strict": false, // Không strict mode (tạm thời)
"target": "ES2020",
"module": "commonjs"
}
}
Step 2: Migrate từng file một
# Rename .js → .ts
mv user.js user.ts
# Fix type errors
# Add types dần dần
Step 3: Enable strict mode từng folder
// tsconfig.json
{
"compilerOptions": {
"strict": true
},
"exclude": [
"src/legacy/**/*" // Exclude legacy code
]
}
Step 4: Gradually type legacy code
Không cần perfect types ngay:
// Phase 1: any everywhere (valid TypeScript)
function processData(data: any): any {
return data.map((item: any) => item.value);
}
// Phase 2: Partial types
function processData(data: any[]): number[] {
return data.map((item: any) => item.value);
}
// Phase 3: Full types
interface DataItem {
value: number;
}
function processData(data: DataItem[]): number[] {
return data.map(item => item.value);
}
TypeScript Best Practices
1. Dùng unknown thay vì any
// Bad
function processData(data: any) {
return data.toUpperCase(); // No error, nhưng có thể crash
}
// Good
function processData(data: unknown) {
if (typeof data === 'string') {
return data.toUpperCase(); // Safe
}
throw new Error('Invalid data');
}
2. Dùng Type Guards
interface User {
type: 'user';
name: string;
}
interface Admin {
type: 'admin';
name: string;
permissions: string[];
}
type Person = User | Admin;
function greet(person: Person) {
if (person.type === 'admin') {
// TypeScript biết person là Admin
console.log(person.permissions);
}
}
3. Avoid type assertions
// Bad
const user = data as User; // Nguy hiểm, không validate
// Good
function isUser(data: unknown): data is User {
return typeof data === 'object'
&& data !== null
&& 'name' in data;
}
if (isUser(data)) {
console.log(data.name); // Safe
}
Cost-Benefit Analysis
TypeScript Costs
Time:
- Setup: 2-4 giờ
- Learning curve: 1-2 tuần (cho devs chưa biết)
- Typing overhead: +10-20% development time
Complexity:
- Thêm build step
- Config (tsconfig.json)
- Type definitions cho third-party libraries
TypeScript Benefits
Fewer Bugs:
- 15-30% fewer bugs (theo research của Microsoft)
- Catch errors trước production
Better Refactoring:
- Safe renames
- Find all usages
- Automated refactoring
Better IDE Support:
- Autocomplete
- Inline documentation
- Jump to definition
Better Onboarding:
- Types là documentation
- New devs hiểu code nhanh hơn
Decision Framework
Project mới?
├─ MVP/Prototype?
│ └─ JavaScript (migrate sau nếu cần)
├─ Production app?
│ ├─ Team < 3 người?
│ │ ├─ Codebase < 5000 dòng?
│ │ │ └─ JavaScript OK
│ │ └─ Codebase > 5000 dòng?
│ │ └─ TypeScript
│ └─ Team > 3 người?
│ └─ TypeScript
└─ Script/Tool?
└─ JavaScript
Project hiện tại (JavaScript)?
├─ Có nhiều bugs do type issues?
│ └─ Migrate sang TypeScript
├─ Team complain code khó maintain?
│ └─ Migrate sang TypeScript
└─ Code chạy ổn, team happy?
└─ Giữ JavaScript
Kết luận
TypeScript NÊN dùng khi:
- Large codebase (> 10,000 dòng)
- Team > 5 người
- Long-term project
- Cần refactor thường xuyên
JavaScript NÊN dùng khi:
- MVP/Prototype
- Scripts/Tools
- Small projects (< 5000 dòng)
- Team chưa biết TypeScript
Lời khuyên:
- Đừng dùng TypeScript vì “trendy”
- Start với JavaScript, migrate khi cần
- TypeScript là tool, không phải religion
- Gradual migration > Big rewrite
Rule of thumb: Nếu project sống > 6 tháng và team > 3 người, TypeScript đáng đầu tư.
Còn lại, JavaScript đủ tốt.
Lợi ích lớn nhất của TypeScript là gì?
Thử Thách Kiến Thức Lịch Sử?
Khám phá hàng trăm câu hỏi trắc nghiệm lịch sử thú vị tại HistoQuiz. Vừa học vừa chơi, nâng cao kiến thức ngay hôm nay!