typescript: ทั่วไปสำหรับประเภทเฉพาะ

Aug 31 2020

ฉันมีประเภททั่วไป

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  },
};

ฉันใช้ประเภททั่วไปเพื่อช่วยสร้าง / พิมพ์ตรวจสอบวัตถุใหม่ที่ฉันสร้างขึ้น

const Obj1: GenericType = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

มันได้ผล. แต่เมื่อฉันใช้วัตถุใหม่ vscode / typescript ไม่แสดงคีย์หรืออุปกรณ์ประกอบฉากของObj1โดยไม่ต้องถอดGenericTypeจากมัน ฉันยังสามารถ "ขยาย" ประเภทได้ แต่เป็นการทำซ้ำรหัส

Obj1.???

มีวิธีการเก็บ GenericType ในขณะที่สามารถเข้าถึงอุปกรณ์ประกอบฉากที่เฉพาะเจาะจงมากขึ้นจากวัตถุใหม่ที่ฉันสร้างขึ้นจากมันได้หรือไม่


อัปเดต 1

ฉันคาดว่า vscode / typescript จะแสดง / ตรวจสอบความถูกต้อง

Obj1.key1.prop1
Obj1.key2.prop1

และเกิดข้อผิดพลาดหาก

Obj1.key1.prop2
Obj1.key2.prop3
Obj1.key2.prop321

คำตอบ

1 MátéSolymosi Aug 31 2020 at 21:47

สมมติว่าเป้าหมายของคุณคือการมีkey1และkey2ปรากฏในเมนูเติมข้อความอัตโนมัติObj1แต่ยังสามารถตั้งค่าคีย์เพิ่มเติมได้ในภายหลังนี่คือวิธีแก้ไขที่เป็นไปได้:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const generify = <T extends GenericType>(obj: T): T & GenericType => obj;

const Obj1 = generify({
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
});

ฉันไม่สามารถคิดวิธีแก้ปัญหาที่ง่ายกว่านี้ได้ในตอนนี้ที่จะให้Obj1ประเภทจุดตัดเดียวกันระหว่างGenericTypeและประเภทเฉพาะที่มีเฉพาะคุณสมบัติkey1และkey2

1 Aron Aug 31 2020 at 21:33

ฉันเห็นกรณีการใช้งานสำหรับสิ่งนี้ที่คุณต้องการGenericTypeให้มีสตริงชนิดใดก็ได้เป็นคีย์ แต่ยังคงต้องการกำหนดว่าค่าของคีย์เหล่านั้นเป็นอย่างไรในจุดที่คุณประกาศ

ในกรณีนี้คุณสามารถใช้Recordประเภทเพื่อ จำกัด คีย์ที่อนุญาตให้ใช้Obj1เฉพาะคีย์ที่คุณระบุ

type GenericType<K extends string> = Record<K, {
  prop1: string,
  prop2?: string,
  prop3?: number,
}>

จากนั้นเมื่อกำหนดObj1คุณสามารถระบุว่าคีย์ที่อนุญาตควรเป็นอย่างไรโดยตั้งค่าการรวมคีย์ที่อนุญาตเป็นพารามิเตอร์ประเภทแรก

const Obj1: GenericType<"key1" | "key2"> = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

ตอนนี้ TypeScript จะช่วยให้คุณเข้าถึงทั้งสองอย่างkey1และkey2ปลอดภัยเต็มรูปแบบ

Obj1.key1
// (property) key1: {
//     prop1: string;
//     prop2?: string | undefined;
//     prop3?: number | undefined;
// }

แก้ไข

จากความคิดเห็นของ OP ดูเหมือนว่าเขาจะไม่ระบุชื่อคีย์ทั้งหมดหรือต้องตรวจสอบว่ามีฟิลด์ที่เป็นทางเลือกด้วยตนเองหรือไม่

วิธีที่ดีที่สุดที่ฉันคิดได้ในขณะที่ยังคงมั่นใจว่าออบเจ็กต์ที่คุณประกาศตรงกับข้อ จำกัด ของGenericTypeอินเทอร์เฟซคือทำสิ่งต่อไปนี้

ก่อนอื่นคุณต้องมียูทิลิตี้ประเภทนี้:

type Constraint<T> = T extends Record<string, {
  prop1: string,
  prop2?: string,
  prop3?: number,
}> ? T : never

สิ่งนี้จะกลับมาneverหากTไม่ตรงกับข้อ จำกัด หรือเพียงTอย่างอื่น

ตอนนี้คุณประกาศวัตถุธรรมดาที่คุณต้องการจริง ไม่มีคำอธิบายประกอบประเภท

const CorrectObj = {
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
};

จากนั้นคุณกำหนดอ็อบเจ็กต์ลิเทอรัลให้กับตัวแปรอื่น แต่ประกาศว่าตัวแปรใหม่ต้องเป็นประเภท Constraint<typeof CorrectObj>

const CheckedObj: Constraint<typeof CorrectObj> = CorrectObj

หากCorrectObjตรงกับข้อ จำกัดCheckedObjจะเป็นสำเนาง่ายๆCorrectObjพร้อมทุกฟิลด์ที่มี

หากลิเทอรัลวัตถุไม่ตรงกับข้อ จำกัด อย่างไรก็ตามคุณจะได้รับข้อผิดพลาดประเภทเมื่อพยายามกำหนด CheckedBadObj ให้กับลิเทอรัล:

const BadObj = {
  key1: {
    progfdgp1: "hi",
  },
  key2: {
    prop1: "bye",
    prdfgop2: "sup",
  },
};

const CheckedBadObj: Constraint<typeof BadObj> = BadObj
//    ^^^^^^^^^^^^^
// Type '{ key1: { progfdgp1: string; }; key2: { prop1: string; prdfgop2: string; }; }' is not assignable to type 'never'. (2322)

คำอธิบายที่ว่าConstraint<T>เมื่อTไม่ตรงกันคือneverแต่คุณยังคงพยายามกำหนดneverค่าที่ไม่ใช่ให้CheckedBadObjทำให้เกิดความขัดแย้งประเภท!

สิ่งนี้เกี่ยวข้องกับการทำซ้ำเล็กน้อยในการประกาศสองอินสแตนซ์ของทุกออบเจ็กต์ตามตัวอักษร แต่เป็นวิธีเดียวของ

  1. การมี TypeScript จะรู้ว่าช่องใดที่มีอยู่บนวัตถุรวมถึงวัตถุย่อยทั้งหมดในขณะที่ยังอยู่
  2. ตรวจสอบว่าค่าประเภท "ทั่วไป" ตรงกับข้อ จำกัด ของคุณ

คุณสามารถเล่นรอบด้วยวิธีนี้ในสนามเด็กเล่น

basarat Sep 07 2020 at 00:11

ใช้ฟังก์ชันยูทิลิตี้ทั่วไป ( enforce) ที่

  • ตรวจสอบให้แน่ใจว่าวัตถุตรงกันGenericTypeโดยใช้ข้อ จำกัด ทั่วไป ( extends)
  • และส่งกลับประเภทของวัตถุที่ส่งผ่านสำหรับการอนุมานประเภท

รหัส:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const enforce = <T extends GenericType>(obj: T): T => obj;

วิธีการทำงาน:

type GenericType = {
  [key: string]: {
    prop1: string,
    prop2?: string,
    prop3?: number,
  };
};

const enforce = <T extends GenericType>(obj: T): T => obj;

const Obj1 = enforce({
  key1: {
    prop1: "hi",
  },
  key2: {
    prop1: "bye",
    prop2: "sup",
  },
});


Obj1.key1.prop1; // Ok
Obj1.key2.prop1; // Ok

/** 
 * ERROR: does not match passed in object
 */
Obj1.key1.prop2 // Error 
Obj1.key2.prop3 // Error
Obj1.key2.prop321 // Error

Obj1.key3; // Error 

/**
 * ERRORS: Does not match GenericType
 */
const Obj2 = enforce({
  key1: { // Error 
  }
});
const Obj3 = enforce({
  key1: {
    prop1: 123, // Error
  }
});