Tại Sao Một Số Gói Cần import * as … Thay Vì import …?
Khi bạn bắt đầu làm việc với JavaScript hoặc TypeScript, bạn sẽ thấy hai phong cách khác nhau của việc nhập thư viện:
javascript
// Phong cách 1
import * as moment from "moment";
// Phong cách 2
import moment from "moment";
Nhìn sơ qua, hai phong cách này có vẻ giống nhau. Nhưng tùy thuộc vào dự án, một phong cách có thể hoạt động trong khi phong cách còn lại gây ra lỗi.
Vậy tại sao một số gói “yêu cầu” import * as? Và khi nào bạn có thể sử dụng phong cách import … sạch hơn? Câu trả lời nằm ở hệ thống module và cài đặt trình biên dịch TypeScript.
Hệ Thống Module: CommonJS vs ES Modules
Có hai cách chính để mã JavaScript được đóng gói:
1. CommonJS (CJS)
Hệ thống Node.js ban đầu. Sử dụng require và module.exports.
javascript
// moment/index.js
module.exports = moment;
javascript
// Sử dụng require
const moment = require("moment");
2. ES Modules (ESM)
Tiêu chuẩn JavaScript hiện đại. Sử dụng import và export.
javascript
// mô-đun kiểu hiện đại
export default PDFDocument;
// Sử dụng import
import PDFDocument from "pdfkit";
👉 Hầu hết các thư viện cũ như Moment.js, PDFKit, và Lodash được viết bằng CommonJS, không phải ESM.
import * as … Làm Gì
Khi bạn viết:
javascript
import * as moment from "moment";
Điều này có nghĩa là:
“Nhập toàn bộ đối tượng module và gán nó cho biến
moment.”
Nếu module chỉ xuất một thứ (như Moment.js), bạn vẫn sẽ nhận được toàn bộ đối tượng dưới cái tên đó.
✅ Điều này luôn hoạt động với các thư viện CommonJS.
❌ Nó có vẻ hơi dài dòng.
import … Làm Gì
Khi bạn viết:
javascript
import moment from "moment";
Điều này có nghĩa là:
“Nhập xuất mặc định từ module.”
Nhưng đây là vấn đề: Các thư viện CommonJS không thực sự có xuất mặc định.
Đó là lý do tại sao trong TypeScript bạn cần bật một số cờ trong file tsconfig.json:
json
{
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
Các tùy chọn này cho TypeScript biết:
👉 “Giả sử rằng các module CommonJS xuất một đối tượng mặc định để chúng tôi có thể sử dụng import x from "y";.”
Ví Dụ Thực Tế
Hãy xem cách điều này diễn ra với một vài thư viện phổ biến.
Ví Dụ 1: Moment.js
javascript
// Hoạt động mọi nơi
import * as moment from "moment";
console.log(moment().format("YYYY-MM-DD"));
javascript
// Sạch hơn, nhưng yêu cầu esModuleInterop
import moment from "moment";
console.log(moment().format("YYYY-MM-DD"));
Ví Dụ 2: PDFKit
javascript
// Nhập không gian tên
import * as PDFDocument from "pdfkit";
const doc = new PDFDocument();
doc.text("Hello, PDFKit!");
doc.end();
javascript
// Nhập xuất mặc định (cần esModuleInterop)
import PDFDocument from "pdfkit";
const doc = new PDFDocument();
doc.text("Hello, PDFKit với nhập xuất mặc định!");
doc.end();
Ví Dụ 3: Lodash
javascript
// Nhập không gian tên
import * as _ from "lodash";
console.log(_.capitalize("hello world"));
javascript
// Nhập xuất mặc định (cần esModuleInterop)
import _ from "lodash";
console.log(_.capitalize("hello world"));
Tại Sao Một Số Dự Án Sử Dụng * as …
- Họ có cài đặt TypeScript nghiêm ngặt (
esModuleInterop: false). - Họ muốn giữ tương thích với các module CommonJS nguyên bản.
- Các cơ sở mã cũ thường giữ phong cách này.
Tại Sao Một Số Dự Án Sử Dụng Nhập Xuất Mặc Định
- Họ đã bật
esModuleInterop: true. - Họ thích cú pháp sạch hơn:
javascript
import moment from "moment";
import PDFDocument from "pdfkit";
import _ from "lodash";
- Nó phù hợp với các quy tắc ESM hiện đại.
Bạn Nên Sử Dụng Cái Nào?
- ✅ Đối với các dự án mới: bật
esModuleInteroptrongtsconfig.jsonvà sử dụngimport x from "package". - ✅ Đối với các dự án cũ hoặc nghiêm ngặt: sử dụng
import * as x from "package". - ✅ Đối với các thư viện hiện đại (như Luxon, Axios, v.v.): luôn sử dụng
import x from "package"— chúng được viết dưới dạng ES Modules.
Sự khác biệt không phải về Moment.js, PDFKit hay Lodash. Nó liên quan đến cách TypeScript cầu nối khoảng cách giữa CommonJS và ES Modules.
import * as X→ Nhập toàn bộ đối tượng module (an toàn cho tất cả các gói CommonJS).import X→ Nhập xuất mặc định (chỉ hoạt động nếu bạn bậtesModuleInterop).
👉 Vì vậy, nếu một gói có vẻ “yêu cầu” * as, đó không phải là lỗi của gói — đó chỉ là cấu hình module của dự án của bạn.
Thực Hành Tốt Nhất
- Nên sử dụng
import ... from ...cho các dự án mới để giữ cho mã nguồn sạch sẽ và dễ đọc. - Kiểm tra các gói thư viện của bạn để đảm bảo tương thích với cài đặt TypeScript hiện tại.
Các Cạm Bẫy Thường Gặp
- Không cấu hình đúng
tsconfig.jsoncó thể dẫn đến lỗi không mong muốn khi nhập thư viện. - Sử dụng kiểu nhập không chính xác có thể gây khó khăn trong việc duy trì mã.
Mẹo Hiệu Năng
- Nên sử dụng các thư viện hiện đại được viết bằng ES Modules để tận dụng tối đa hiệu suất và tính năng mới nhất.
- Tránh nhập toàn bộ không gian tên nếu bạn chỉ cần một phần nhỏ của thư viện.
Giải Quyết Vấn Đề
- Nếu bạn gặp lỗi khi sử dụng
import ..., kiểm tra cài đặttsconfig.jsonvà đảm bảo rằngesModuleInteropđược bật nếu bạn muốn sử dụng nhập xuất mặc định.
Câu Hỏi Thường Gặp
1. Tại sao tôi không thể sử dụng import ... với một thư viện CommonJS?
Bạn cần đảm bảo rằng esModuleInterop được bật trong tsconfig.json để sử dụng cú pháp này.
2. Tôi có thể chuyển đổi dự án cũ sang ES Module không?
Có, nhưng bạn cần phải kiểm tra các thư viện và điều chỉnh mã của mình cho phù hợp với cú pháp mới.
3. Có cách nào để kiểm tra xem một gói có hỗ trợ nhập xuất mặc định không?
Bạn có thể xem tài liệu của gói hoặc kiểm tra mã nguồn để xác định cách nó xuất các module.
Hy vọng rằng bài viết này đã giúp bạn hiểu rõ hơn về sự khác nhau giữa import * as và import ..., cũng như cách sử dụng chúng đúng cách trong dự án của bạn. Hãy thực hành và áp dụng những gì bạn đã học để cải thiện kỹ năng lập trình của mình!