Modules, Import và Export trong TypeScript
Bài 10 – Cách dùng module, import và export trong TypeScript theo cách đơn giản, trực quan và tiêu chuẩn.
Modules, Import và Export trong TypeScript
Module giúp bạn tách code thành từng phần rõ ràng, dễ bảo trì và dễ mở rộng. TypeScript sử dụng hệ thống module chuẩn ECMAScript, cho phép import/export mạnh mẽ, linh hoạt và an toàn kiểu.
1. Nội dung chính
- Module là gì và tại sao cần module
- Export: named export, default export
- Import: cách import chuẩn và thực tế
- Cách tổ chức module trong dự án
- Lưu ý khi dùng module trong Node.js và bundler
2. Ví dụ chi tiết
2.1. Named Export và Import
// 📁 math.ts - Multiple named exports
export function sum(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export const PI = 3.14159;
export const E = 2.71828;
export const GOLDEN_RATIO = 1.61803;
// Export types
export type MathOperation = (a: number, b: number) => number;
export interface Calculator {
add: MathOperation;
subtract: MathOperation;
multiply: MathOperation;
divide: MathOperation;
}
// 📁 app.ts - Import named exports
import { sum, subtract, PI, E } from "./math";
console.log(sum(10, 5)); // 15
console.log(subtract(10, 5)); // 5
console.log(PI); // 3.14159
console.log(E); // 2.71828
// Import với alias
import { sum as add, subtract as minus } from "./math";
console.log(add(3, 4)); // 7
console.log(minus(10, 3)); // 7
2.2. Default Export
// 📁 calculator.ts - Single default export
export default class Calculator {
private result: number = 0;
add(value: number): this {
this.result += value;
return this;
}
subtract(value: number): this {
this.result -= value;
return this;
}
multiply(value: number): this {
this.result *= value;
return this;
}
divide(value: number): this {
if (value === 0) throw new Error("Cannot divide by zero");
this.result /= value;
return this;
}
getResult(): number {
return this.result;
}
reset(): this {
this.result = 0;
return this;
}
}
// 📁 app.ts - Import default export
import Calculator from "./calculator";
const calc = new Calculator();
const result = calc
.add(10)
.subtract(3)
.multiply(2)
.divide(2)
.getResult();
console.log(result); // 7
2.3. Mixed Export (Named + Default)
// 📁 utils.ts - Mixed exports
// Named exports
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
export function reverse(str: string): string {
return str.split('').reverse().join('');
}
export const APP_NAME = "MyTypeScriptApp";
export const VERSION = "1.0.0";
// Default export
export default function slugify(str: string): string {
return str
.toLowerCase()
.trim()
.replace(/[\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
// 📁 app.ts - Import mixed exports
import slugify, { capitalize, reverse, APP_NAME } from "./utils";
console.log(capitalize("hello world")); // "Hello world"
console.log(reverse("hello")); // "olleh"
console.log(slugify("Hello World!")); // "hello-world"
console.log(APP_NAME); // "MyTypeScriptApp"
2.4. Import All (Namespace Import)
// 📁 constants.ts
export const API_URL = "https://api.example.com";
export const API_VERSION = "v1";
export const TIMEOUT = 5000;
export const MAX_RETRIES = 3;
export const DEFAULT_LANGUAGE = "en";
export function getApiEndpoint(path: string): string {
return `${API_URL}/${API_VERSION}/${path}`;
}
// 📁 app.ts - Import all as namespace
import * as Constants from "./constants";
console.log(Constants.API_URL); // "https://api.example.com"
console.log(Constants.getApiEndpoint("users")); // "https://api.example.com/v1/users"
console.log(Constants.TIMEOUT); // 5000
// Sử dụng destructuring từ namespace
const { API_URL, TIMEOUT, getApiEndpoint } = Constants;
console.log(API_URL); // "https://api.example.com"
console.log(getApiEndpoint("products")); // "https://api.example.com/v1/products"
2.5. Dynamic Import (Code Splitting)
// 📁 heavy-module.ts
export function heavyCalculation(n: number): number {
// Simulate heavy computation
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
return result;
}
export const largeDataset = Array.from({ length: 10000 }, (_, i) => ({
id: i,
data: Math.random() * 1000
}));
// 📁 app.ts - Dynamic import
async function loadHeavyModule() {
try {
// Dynamic import - loaded only when needed
const { heavyCalculation, largeDataset } = await import("./heavy-module");
console.log("Heavy module loaded!");
const result = heavyCalculation(1000);
console.log("Calculation result:", result);
console.log("Dataset size:", largeDataset.length);
} catch (error) {
console.error("Failed to load heavy module:", error);
}
}
// Load on button click or specific condition
button.addEventListener("click", loadHeavyModule);
2.6. Re-export (Barrel Export)
// 📁 components/index.ts - Barrel export
export { Button } from "./Button";
export { Input } from "./Input";
export { Card } from "./Card";
export { Modal } from "./Modal";
export type { ButtonProps } from "./Button";
export type { InputProps } from "./Input";
// 📁 app.ts - Clean imports từ barrel
import { Button, Input, Card, Modal } from "./components";
import type { ButtonProps, InputProps } from "./components";
3. Kiến thức trọng tâm
3.1. Module giúp chia nhỏ code để dễ quản lý
- Mỗi file trở thành một module độc lập, giúp dự án rõ ràng, sạch và dễ tìm kiếm
- Namespace isolation - tránh conflicts giữa các phần của ứng dụng
- Better organization - code có structure rõ ràng và logical
3.2. Named export dùng để xuất nhiều thứ trong một file
- Thường dùng cho utility, constants, và hàm chung
- Type-safe imports - TypeScript catch lỗi import ngay từ compile time
- Tree-shaking friendly - bundlers có thể loại bỏ unused exports
3.3. Default export dành cho “một giá trị chính”
- Giúp import ngắn gọn, dễ đọc
- Mỗi file chỉ có một default export
- Phù hợp cho classes, main functions, hoặc single-purpose modules
💡 GHI NHỚ: Một file có thể có cả named và default exports, nhưng chỉ một default export
4. Bài tập thực hành
Bài 1: Tạo file utils.ts với named exports
// 📁 utils.ts
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
export function slugify(str: string): string {
return str
.toLowerCase()
.trim()
.replace(/[\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
export const VALIDATION_RULES = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^\+?[\d\s-()]+$/,
url: /^https?:\/\/.+/
} as const;
Bài 2: Tạo file main.ts để import và test
// 📁 main.ts
import { capitalize, slugify, VALIDATION_RULES } from "./utils";
// Test capitalize
console.log(capitalize("hello world")); // "Hello world"
console.log(capitalize("JAVASCRIPT")); // "Javascript"
// Test slugify
console.log(slugify("Hello World!")); // "hello-world"
console.log(slugify("User@Name#123")); // "username123"
// Test validation
console.log(VALIDATION_RULES.email.test("user@example.com")); // true
console.log(VALIDATION_RULES.email.test("invalid-email")); // false
Bài 3: Tạo module constants.ts
// 📁 constants.ts
export const API_URL = "https://api.example.com";
export const API_VERSION = "v1";
export const APP_NAME = "MyAwesomeApp";
export const VERSION = "1.0.0";
export const DEFAULT_TIMEOUT = 3000;
export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
export const HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_ERROR: 500
} as const;
export function getFullApiUrl(endpoint: string): string {
return `${API_URL}/${API_VERSION}/${endpoint}`;
}
5. Best Practices và Lưu ý
5.1. Node.js và TypeScript
// tsconfig.json cho Node.js
{
"compilerOptions": {
"module": "commonjs", // Cho Node.js < 14
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
// package.json với type: module (Node.js >= 14)
{
"type": "module",
"scripts": {
"dev": "node --loader ts-node/esm src/index.ts"
}
}
5.2. Webpack và Bundlers
// Tree-shaking optimization
// 📁 utils.ts
export const utils = {
format: (str: string) => str.trim(),
parse: (str: string) => str.split(','),
validate: (str: string) => str.length > 0
};
// Better for tree-shaking
export const format = (str: string) => str.trim();
export const parse = (str: string) => str.split(',');
export const validate = (str: string) => str.length > 0;
5.3. Circular Dependencies
// ❌ Tránh circular dependencies
// file-a.ts
import { functionB } from "./file-b";
export function functionA() { return functionB(); }
// file-b.ts
import { functionA } from "./file-a";
export function functionB() { return functionA(); }
// ✅ Sử dụng interfaces hoặc shared types
// types.ts
export interface SharedInterface { /* ... */ }
// file-a.ts
import { SharedInterface } from "./types";
6. Sai lầm thường gặp
- Import sai path - không có
./hoặc quên extension - Circular dependencies - A import B, B import A
- Mixed module systems - dùng cả CommonJS và ES Modules
- Over-exporting - export quá nhiều thứ không cần thiết
- Under-documenting - không có JSDoc cho exported functions
- Large default exports - default export quá lớn và complex
⚠️ GHI NHỚ: Luôn kiểm tra circular dependencies với tools như
madge
7. Kết luận
Sử dụng module đúng cách giúp dự án rõ ràng, dễ bảo trì và dễ mở rộng. Đây là kỹ năng bắt buộc khi làm việc trong bất kỳ codebase TypeScript nào, đặc biệt ở môi trường doanh nghiệp.
🔑 GHI NHỚ QUAN TRỌNG:
- Named exports cho utilities và constants
- Default exports cho main classes/functions
- Barrel exports để clean up import paths
- Dynamic imports cho code splitting
- Type imports để tránh circular dependencies
- Document exports với JSDoc comments