ค้นหาเว็บไซต์

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 ไปยังโค้ด