Hàm khởi tạo (constructor) trong TypeScript là phương thức constructor của class với các tham số có kiểu. Nó không có chú thích kiểu trả về: nó luôn tạo ra một instance của class.
Viết constructor(): Product là lỗi TS1093 (Type annotation cannot appear on a constructor declaration). Mọi thứ khác về cách new hoạt động đều là JavaScript thuần.
Parameter property
Mẫu ở trên (khai báo thuộc tính, nhận tham số, chép giá trị sang) phổ biến đến mức TypeScript có cách viết tắt. Đặt public, private, protected hoặc readonly trước một tham số của hàm khởi tạo và nó trở thành một thuộc tính:
Đây là một trong số ít tính năng của TypeScript có sinh ra code. Trình biên dịch viết các phép gán thay bạn:
class Product {
name;
price;
sku;
constructor(name, price, sku) {
this.name = name;
this.price = price;
this.sku = sku;
}
// ...
}
Vì đây không chỉ là kiểu bị xóa đi, parameter property không chạy được với cơ chế bỏ kiểu có sẵn của Node (node file.ts thất bại với ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX), và tùy chọn biên dịch erasableSyntaxOnly báo chúng là lỗi TS1294. Các dự án chạy trực tiếp tệp .ts sẽ tự viết đầy đủ các thuộc tính.
Tham số tùy chọn và tham số mặc định
Tham số của hàm khởi tạo theo cùng quy tắc với tham số của hàm: ? làm một tham số thành tùy chọn, giá trị mặc định làm nó tùy chọn và tự điền giá trị, còn các tham số bắt buộc đứng trước.
Parameter property cũng có thể có giá trị mặc định. Kiểu của color bên trong class là string | undefined, nên code dùng nó phải kiểm tra undefined trước.
Với hàm khởi tạo có nhiều tùy chọn, một đối tượng tùy chọn dễ đọc hơn một danh sách tham số dài theo vị trí: constructor(opts: { prefix: string; level?: "info" | "debug" }).
Thứ tự khởi tạo thuộc tính
Giá trị khởi tạo của thuộc tính chạy trước thân hàm khởi tạo, còn parameter property được gán ở đầu thân hàm khởi tạo. Vì vậy giá trị khởi tạo của thuộc tính không đọc được parameter property. TypeScript bắt được lỗi này:
index.ts(3,18): error TS2729: Property 'size' is used before its initialization.
index.ts(3,30): error TS2729: Property 'size' is used before its initialization.
Khi chạy, giá trị khởi tạo sẽ thấy undefined và cho ra NaN. Hãy tính giá trị đó trong thân hàm khởi tạo:
class Grid {
cells: number;
constructor(public size: number) {
this.cells = size * size;
}
}
console.log(new Grid(3).cells); // 9
Overload cho hàm khởi tạo
Một class có đúng một phần hiện thực hàm khởi tạo, nhưng bạn có thể liệt kê nhiều chữ ký overload phía trên nó. Nơi gọi chỉ thấy các overload; chữ ký của phần hiện thực phải tương thích với tất cả.
Overload khó đọc khi vượt quá hai hoặc ba dạng. Các phương thức factory static có tên mô tả rõ (Color.fromHex("#ff8800"), Color.fromRgb(10, 20, 30)) thường rõ ràng hơn và không cần thu hẹp kiểu bên trong một thân hàm.
Gọi super trong class con
Một class dẫn xuất tự khai báo hàm khởi tạo phải gọi super(...) với các đối số của class cha, và phải gọi trước khi đụng đến this. Quên gọi là lỗi TS2377 (Constructors for derived classes must contain a 'super' call), còn dùng this trước là TS17009 ('super' must be called before accessing 'this' in the constructor of a derived class). Cả hai đều phản ánh quy tắc lúc chạy của JavaScript.
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
tricks: string[];
constructor(name: string, ...tricks: string[]) {
super(name); // must come first
this.tricks = tricks;
}
}
const rex = new Dog("Rex", "sit", "roll");
console.log(rex); // Dog { name: 'Rex', tricks: [ 'sit', 'roll' ] }
Một class con không khai báo hàm khởi tạo sẽ kế thừa hàm khởi tạo của class cha, với cùng kiểu tham số. Xem thêm về class con ở kế thừa.
Hàm khởi tạo private và khởi tạo bất đồng bộ
Hàm khởi tạo private chỉ gọi được từ bên trong class. Ở bên ngoài, new là lỗi TS2673. Đây là cách buộc nơi gọi đi qua một factory, cho singleton hoặc cho phần khởi tạo phải chạy bất đồng bộ (hàm khởi tạo không thể là async).
private ở đây chỉ là quy tắc lúc biên dịch. JavaScript được sinh ra có một hàm khởi tạo bình thường, nên code JavaScript thuần vẫn có thể gọi new Connection(...).
Kiểu hàm khởi tạo
Để nhận một class làm giá trị, hãy gán kiểu cho tham số bằng construct signature: new (...args) => Instance. typeof MyClass cũng dùng được, nhưng nó gắn tham số với đúng chữ ký hàm khởi tạo và các thành viên static của class đó.
ConstructorParameters và InstanceType trích xuất tuple tham số và kiểu instance từ một kiểu hàm khởi tạo; chúng được trình bày cùng các helper khác cho hàm ở trang ReturnType và Parameters.
Câu hỏi thường gặp
Parameter property trong TypeScript là gì?
Một tham số của hàm khởi tạo có modifier (public, private, protected hoặc readonly) sẽ khai báo một thuộc tính và gán nó trong một bước. constructor(private name: string) {} là cách viết tắt của việc khai báo private name: string rồi viết this.name = name. Trình biên dịch sinh ra phép gán đó trong kết quả.
Một class TypeScript có thể có nhiều hàm khởi tạo không?
Không thể có nhiều phần hiện thực. Một class chỉ có một hàm khởi tạo, nhưng bạn có thể viết nhiều chữ ký overload phía trên nó để nơi gọi thấy các danh sách tham số khác nhau. Các phương thức factory static như Color.fromHex() và Color.fromRgb() thường rõ ràng hơn overload.
Hàm khởi tạo trong TypeScript có thể là async không?
Không. Hàm khởi tạo luôn trả về instance mới, không bao giờ trả về promise, và async constructor() bị từ chối với lỗi TS1089 ('async' modifier cannot appear on a constructor declaration). Hãy dùng một hàm khởi tạo private cùng một phương thức static async create() làm phần việc bất đồng bộ rồi gọi new.
Làm sao gán kiểu cho hàm khởi tạo của class khi truyền làm tham số?
Dùng construct signature: new (name: string) => User, hoặc typeof User cho đúng class đó. Một factory generic trông như sau: function make<T>(ctor: new () => T): T { return new ctor(); }. ConstructorParameters<typeof User> cho ra danh sách tham số dưới dạng tuple.
Vì sao xuất hiện lỗi "'super' must be called before accessing 'this'"?
Đó là lỗi TS17009. Trong một class extends class khác, hàm khởi tạo của class cha tạo ra đối tượng, nên this chưa tồn tại cho đến khi super(...) chạy xong. Hãy chuyển lời gọi super lên trên mọi dòng có dùng this.