javascript - จะอธิบายการสืบทอดแบบโปรโตไทป์ใน Typescript อย่างไร?
โค้ดด้านล่างเป็น Javascript ที่ถูกต้อง Typescript อย่างมีเหตุผล ไม่เข้าใจการอ้างอิง this.a ของ B และจึงเตือน มีวิธีการใช้ type-fu กับ B เพื่ออธิบายสถานการณ์เวลารันให้ Typescript เข้าใจและได้ประโยชน์ด้านความปลอดภัยของชนิดข้อมูลหรือไม่ (เช่น การเกิดข้อผิดพลาดถ้าพยายามเข้าถึง this.c)?
const A = {
a: 1,
bar() {
this.a = 10;
},
};
const B = {
b: 2,
f() {
console.log(this.a + this.b);
},
bar() {
super.bar();
console.log(this.a);
},
};
Reflect.setPrototypeOf(B, A);
B.f(); // 3
B.bar(); // 10คำตอบ #1
ฉันคิดว่าหนทางเดียวที่คุณจะทำให้สิ่งแบบนี้ทำงานได้ (โดยไม่ต้องเขียนข้อมูลการระบุประเภทที่ซ้ำซ้อนทุกที่) คือถ้าคุณปรับโครงสร้างการกำหนดค่า B และการตั้งค่า prototype ของมันให้เป็นการเรียกฟังก์ชันเดียวแบบนี้:
function setPrototype<T extends object, U extends object>(
object: T & ThisType<T & U>,
proto: U
) {
Reflect.setPrototypeOf(object, proto);
return object as T & U;
}
ถ้าคุณเรียก setPrototype({...}, proto) โดยที่ {...} เป็นวัตถุลิตเทอรัลบางชนิดของชนิดทั่วไป T และที่ proto เป็นวัตถุอื่นบางชนิดของชนิดทั่วไป U ตัวคอมไพเลอร์จะตั้งโปรโตไทป์ของวัตถุลิตเทอรัลเป็น proto และส่งมันกลับมา
ในการเรียกนี้ คุณจะเห็นว่าออบเจ็กต์ลิเทอรัลจะได้รับประเภทเชิงบริบทสำหรับ this ซึ่งเป็นการตัดกันของทั้ง T และ U สิ่งนี้เกิดขึ้นผ่านยูทิลิตี้ไทป์เวิร์ก ThisType<T> เมื่อผมพูดว่า "เวทมนตร์" ที่นี่ ผมหมายความว่า ต่างจากยูทิลิตี้ไทป์ส่วนใหญ่ คุณไม่สามารถกำหนดไทป์เทียบเท่าด้วยตัวเองได้ คอมไพเลอร์จะให้พลังเชิงบริบทพิเศษแก่ ThisType<T> โดยตรง; ดู microsoft/TypeScript#14141 สำหรับคำอธิบายว่ามันทำงานอย่างไร
สุดท้าย ชนิดของวัตถุที่ส่งกลับมาก็จะมีการกำหนดชนิด T & U แบบตัดกันด้วย ดังนั้นคุณจึงสามารถเข้าถึงทั้งคุณสมบัติของตัวมันเองและคุณสมบัติที่สืบทอดมาจากต้นแบบได้
ลองทำดู:
const A = {
a: 1,
bar() {
this.a = 10;
},
};
const B = setPrototype({
b: 2,
f() {
console.log(this.a + this.b);
},
bar() {
super.bar();
console.log(this.a);
},
}, A);
B.f(); // 3
B.bar(); // 10
ดูดี! ตอนนี้ไม่มีข้อผิดพลาดแล้ว และตัวคอมไพเลอร์สามารถสรุปชนิดตามบริบทสำหรับ this.a และ this.b ภายในการใช้งานของ f()
มีข้อควรระวังและกรณีที่เฉพาะเจาะจง ประเภทตัดกัน T & U อาจไม่ถูกต้องสมบูรณ์ แต่ก็เป็นสิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถแสดงได้อย่างง่ายดาย
ข้อควรระวังที่ใหญ่กว่าที่นี่คือดูเหมือนว่าจะไม่มีวิธีจัดการกับ super ในลักษณะที่ปลอดภัยต่อประเภท ในขณะนี้ถ้าไม่มีสิ่งเหมือนการใส่คำอธิบายพารามิเตอร์ super (ตามที่ร้องขอใน microsoft/TypeScript#42327) จะไม่มีวิธีทำด้วยตนเอง และแม้ว่าคุณจะสามารถทำด้วยตนเอง ก็ยังจะไม่สามารถไปถึงการกำหนดประเภทตามบริบทอัตโนมัติของ ThisType<T>... คุณก็จะต้องการ alias ประเภทเวทมนตร์ใหม่อย่าง SuperType<T> ด้วย ดังนั้นสำหรับตอนนี้ ดูเหมือนว่าสิ่งนี้เกินความสามารถของ TypeScript
ส่วนตัวผมขอแนะนำให้เลิกใช้วิธีสลับโปรแกรมต้นแบบแบบแมนนวลนี้ แล้วหันไปใช้การสืบทอดแบบ class แทน เพราะมีการทำงานกับ TypeScript เพื่อรองรับการสืบทอด ES2015+ มากกว่าการรองรับ ES5- แต่คุณคงมีเหตุผลของคุณในการทำแบบนี้ หวังว่าโค้ดข้างต้นจะช่วยให้คุณเข้าใกล้การมีความปลอดภัยของชนิดข้อมูลที่ใช้งานได้มากขึ้น
ลิงก์ Playground ไปยังโค้ด
