typescript - Sử dụng generics bên trong phương thức
Tại sao tôi lại nhận được lỗi "Kiểu 'AbstractPopup' không thể gán cho kiểu T" khi trả về từ phương thức popupFactory(...)? Vui lòng bỏ qua các lỗi khác, đây chỉ là mã thử nghiệm để hiểu rõ hơn về cách hoạt động của generic.
function popupFactory<T extends AbstractPopup>(popupType: PopupType, data: {}): T
{
var popup: AbstractPopup;
switch (popupType)
{
case PopupType.ConcretePopup:
{
popup = new ConcretePopup();
break;
}
}
return popup;
}
abstract class AbstractPopup
{
type: PopupType;
}
class ConcretePopup extends AbstractPopup{}
var p = popupFactory<ConcretePopup>(PopupType.ConcretePopup, {});
Câu trả lời #1
Một hàm tổng quát ngụ ý rằng nó nên hoạt động dựa trên tham số kiểu tổng quát được truyền cho nó. Trong trường hợp của bạn, phương thức quyết định kiểu trả về dựa trên tham số enum, không dựa trên tham số tổng quát. Vì vậy, phương thức thực sự không phải là tổng quát.
Lý do trình biên dịch báo lỗi là vì, do bạn có thể truyền bất kỳ T nào được dẫn xuất từ AbstractPopup, nên nó thực sự không thể kiểm tra rằng kiểu dữ liệu bạn trả về sẽ tương thích với T cụ thể mà bạn truyền vào hàm. Hãy xem xét đoạn mã hợp lệ sau:
class OtherConcretePopup extends AbstractPopup{}
var p = popupFactory<OtherConcretePopup>(PopupType.ConcretePopup, {})
Mã sẽ hợp lệ, nhưng bạn đã trả về một ConcretePopup mặc dù kiểu trả về là OtherConcretePopup
Giải pháp đơn giản nhất là có nhiều phương thức nạp chồng cho mỗi loại cửa sổ bật lên:
function popupFactory(popupType: PopupType.OtherConcretePopup, data: {}): OtherConcretePopup
function popupFactory(popupType: PopupType.ConcretePopup, data: {}): ConcretePopup
function popupFactory(popupType: PopupType, data: {}): AbstractPopup
{
var popup: AbstractPopup = null;
switch (popupType)
{
case PopupType.ConcretePopup:
{
popup = new ConcretePopup();
break;
}
}
return popup;
}
Hoặc bạn có thể truyền constructor như một tham số và loại bỏ hoàn toàn nhu cầu sử dụng switch, nhưng đây chỉ là một lựa chọn tốt nếu tất cả các constructor có cùng số lượng tham số và việc khởi tạo giống nhau cho tất cả:
function popupFactory<T extends AbstractPopup>(popupType: new() => T, data: {}): T
{
var popup = new popupType();
return popup;
}
var p = popupFactory(ConcretePopup, {})
Câu trả lời #2
Thay vì sử dụng switch, bạn có thể sử dụng phương thức factory tổng quát dưới đây để tạo thể hiện của bất kỳ lớp dẫn xuất nào. Lưu ý chữ ký new() => T ở đây, nó biểu thị một kiểu có constructor không có đối số, vì vậy bạn có thể trực tiếp truyền tên lớp dẫn xuất ở đây nếu nó có constructor mặc định không có đối số như dưới đây -
function popupFactory<T extends AbstractPopup>(popupType: new () => T, data: {}): T
{
var newPopup: T;
newPopup = new popupType();
return newPopup;
}
abstract class AbstractPopup
{
//type: PopupType;
}
class ConcretePopup extends AbstractPopup{
}
var p = popupFactory(ConcretePopup, {}); // creates instance of ConcretePopup class
Nhưng nếu lớp dẫn xuất của bạn có 1 hoặc nhiều tham số trong hàm khởi tạo và bạn cần thiết lập giá trị tham số khi tạo thể hiện của lớp, bạn có thể theo cách tiếp cận dưới đây -
function popupFactory<T extends AbstractPopup>(popupType: new (obj:Object) => T, data: {}): T
{
var newCust: T;
newCust = new popupType(data);
return newCust;
}
abstract class AbstractPopup
{
//type: PopupType;
}
class ConcretePopup extends AbstractPopup{
public fname: string;
public lname: string;
constructor(obj:Object) {
super();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (key == "fname") {
this.fname = obj["fname"];
}
if (key == "lname") {
this.lname = obj["lname"];
}
}
}
}
}
var p = popupFactory(ConcretePopup, {"fname":"Niladri","lname":"D"});
console.log(p.fname); // Niladri
console.log(p.lname); // D
Ở đây tôi đang truyền popupType: new (obj:Object) => T như một kiểu nhận một Object làm tham số trong hàm tạo của nó. Vì vậy, chúng ta có thể truyền các thuộc tính và giá trị cần thiết dưới dạng một đối tượng, việc này được thực hiện thông qua tham số data ở đây. Nhưng nhược điểm của phương pháp này là bạn phải tự tay trích xuất các thuộc tính cho lớp dẫn xuất như bạn có thể thấy trong hàm tạo của lớp ConcretePopup ở trên.
Đây là một liên kết đến một fiddle hoạt động
Mẫu mã
