typescript - มีประเภทของคลาสที่ยังไม่ได้สร้างอินสแตนซ์หรือไม่?
สมมติว่าโค้ดต่อไปนี้:
interface Foobar {}
class Foo implements Foobar {}
class Bar implements Foobar {}
const arr: Foobar[] = [Foo, Bar];
สิ่งนี้สามารถคอมไพล์ได้ แต่ มันไม่ใช่ความจริงทั้งหมด typeof Foo หรือ typeof Bar เป็น Function - มีเหตุผล เพราะพวกมันยังไม่ได้ถูกสร้างอินสแตนซ์จนถึงตอนนี้
มีวิธีที่ดีกว่าในการจัดเก็บคลาสที่ยังไม่ได้สร้างอินสแตนซ์มากกว่า arr: any[] ไหม?
คำตอบ #1
อินเทอร์เฟซว่างที่นี่ทำให้คุณสับสน; โค้ดนี้ใช้งานได้เพียงเพราะอินเทอร์เฟซไม่มีสมาชิกใด ๆ (และด้วยเหตุนี้จึงหมายถึง "อะไรก็ตาม"):
interface Foobar { x: number; }
class Foo implements Foobar { x: number; }
class Bar implements Foobar { x: number; }
// Error
const arr: Foobar[] = [Foo, Bar];
ประเภททั่วไปของคลาสคือ { new(...args: any[]): any } หากคุณกำลังพูดถึงบางสิ่งที่สามารถถูกเรียกใช้ด้วย new หรือ { new(...args: any[]): Foobar } หากคุณกำลังพูดถึงบางสิ่งที่ส่งกลับ Foobar เมื่อถูกเรียกใช้ด้วย new.
คำตอบ #2
interface Foobar {}
class Foo implements Foobar {}
class Bar implements Foobar {}
const arr: Foobar[] = [Foo, Bar];
นี่คอมไพล์ได้ แต่จริงๆ แล้วมันไม่ใช่ความจริง typeof Foo หรือ typeof Bar คือ Function - เหมาะสมแล้ว เพราะว่าพวกมันยังไม่ได้ถูกสร้างอินสแตนซ์เลยจนถึงตอนนี้
คลาสคือค่าหนึ่ง อินสแตนซ์คือค่าหนึ่ง ไม่มีสิ่งที่เรียกว่า คลาสที่ไม่ได้ถูกสร้างอินสแตนซ์
มันมีประโยชน์อย่างมากที่จะสามารถเก็บคลาสได้ ไม่ใช่แค่เฉพาะอินสแตนซ์ของมัน
ยิ่งไปกว่านั้น เนื่องจากคลาสเป็นเพียงฟังก์ชัน ความสามารถในการทำเช่นนั้นจึงเป็นผลลัพธ์ตามธรรมชาติ เป็นคุณสมบัติที่ทรงพลังซึ่งเราได้รับโดยไม่ต้องเสียค่าใช้จ่าย
มีวิธีที่ดีกว่าในการเก็บคลาสที่ยังไม่ได้สร้างวัตถุมากกว่า arr: any[] ไหม?
สำหรับประเภทของอาเรย์ของพวกมัน ใช่ คุณมีตัวเลือกที่ดีกว่า any[] อย่างแน่นอน อย่าใช้ any[] สำหรับสิ่งนี้เพราะประเภทเป็นแบบละเอียดและยังสามารถอนุมานได้ง่าย
วิธีที่ง่ายที่สุดคือปล่อยให้คอมไพเลอร์เดาประเภทเอง วิธีนี้มีความแม่นยำสูงและเขียนง่าย (เพียงเขียนโค้ดเพื่อลบ ไม่ใช่เพิ่ม)
const arr = [Foo, Bar];
อีกวิธีหนึ่งคือการใช้ประเภททูเพิล
const arr: [typeof Foo, typeof Bar] = [Foo, Bar];
และสุดท้ายคุณสามารถใช้ชนิดของยูเนียนแบบชัดเจน
const arr: Array<typeof Foo | typeof Bar> = [Foo, Bar];
สำหรับการใช้คลาสโดยไม่ตั้งใจ ลืม new มัน คำตอบของ Ryan Cavanaugh ครอบคลุมเรื่องนั้นได้อย่างสมบูรณ์
ตอบกลับความคิดเห็น
นี่น่าจะทำให้การใช้งานอินเทอร์เฟซค่อนข้างไร้ประโยชน์ใช่ไหม?
อินเทอร์เฟซ (interface) คือการประกาศที่ใช้ในการอธิบายชนิดของค่าหรือข้อมูลใด ๆ
TypeScript เป็นภาษาแบบชนิดเชิงโครงสร้าง (หรือที่เรียกว่า duck typed) ซึ่งหมายความว่าค่าทุกค่าจะใช้อินเทอร์เฟซได้เพียงโดยมีสมาชิกที่เข้ากันได้
คำหลัก implements เป็น อุปกรณ์เสริม เสมอในนิยามคลาส มักใช้เพื่อสื่อเจตนาเพื่อช่วยเครื่องมือและโปรแกรมเมอร์คนอื่นเข้าใจโค้ดได้ดีขึ้น แต่ไม่เปลี่ยนประเภทจริงของมัน
คุณอาจกำลังคิดถึงการสั่งงานแบบ interface ดังนั้นประเภท FooBar[] ในตัวอย่างของคุณ แต่ใน TypeScript เช่นเดียวกับใน JavaScript การมีพหุรูปร่าง (polymorphism) จะบรรลุผลผ่านเทคนิคที่แตกต่างกันโดยสิ้นเชิง
นี่คือตัวอย่างของการใช้ interface เพื่ออธิบายอินสแตนซ์ของคลาส:
interface I {
p: string;
m(): void;
}
class A implements I {
p = 'implements I';
m() {console.log(this.p);}
}
const a = new A;
class B {
p = 'also implements I';
m() {console.log(this.p);}
}
const b = new B;
const c = {
p: 'also implements I',
m() {console.log(this.p);}
};
const is: I[] = [a, b, c];
อินเทอร์เฟซ I ข้างต้นอธิบายรูปร่างของอินสแตนซ์ของ คลาส A รูปร่างของอินสแตนซ์ของอินสแตนซ์ของ คลาส B และรูปร่างของออบเจ็กต์ c
ด้วยเหตุนี้ การที่คลาสเองสามารถนำอินเทอร์เฟซไปใช้งานได้เองก็ถือเป็นเรื่องธรรมชาติ
อินเทอร์เฟซดังกล่าวอธิบายรูปทรงของค่าคลาสเอง ไม่ใช่ของอินสแตนซ์ของมัน
จงจำไว้ว่า คลาสใน JavaScript เป็นเพียงฟังก์ชันที่สามารถถูก "สร้างใหม่" (ทางเทคนิคมากขึ้นคือ [[Construct]]ed)
ด้วยเหตุนี้ อินเทอร์เฟซที่แทนคลาสใด ๆ สามารถเขียนได้
interface Newable<T> {
new(...args: any[]): T;
}
นี่บางครั้งถูกเรียกว่าประเภทของด้านคงที่ของคลาสเนื่องจากอนุญาตให้ระบุสมาชิก static ในอินเทอร์เฟซได้
interface Deserializable<T> {
fromJSON(json: string): T;
}
interface NewableDeserializable<T> extends Newable<T>, Deserializable<T> {}
ตอนนี้พิจารณาอินเทอร์เฟซและคลาสต่อไปนี้
interface Character {
position: {x: number, y: number, z: number};
name: string;
}
class Snake implements Character {
position = {x: 0, y: 0, z: 0};
name = 'Snake';
static fromJSON(saved: string) {
const mc = new this();
return Object.assign(mc, JSON.parse(saved));
}
}
interface NewableDeserializable<T> extends Newable<T>, Deserializable<T> {}
const deserializableMainCharacterClass: NewableDeserializable<Character> = Snake;
การระบุชนิดข้อมูลบนตัวแปรเป็นทางเลือก แต่แสดงให้เห็นว่า คลาสเอง ใช้อินเทอร์เฟซนั้นเพราะว่ามันตรวจสอบชนิดได้
สุดท้าย สิ่งเหล่านี้ทั้งหมดสามารถทำได้ในหนึ่งนิพจน์:
const Meryl: NewableDeserializable<Character> =
class implements Character {
name = 'Meryl';
position = {x: 1, y: 1, z: 1};
static fromJSON(saved: string) {
const mc = new this();
return Object.assign(mc, JSON.parse(saved));
}
};
