typescript: ทั่วไปสำหรับประเภทเฉพาะ
ฉันมีประเภททั่วไป
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
คำตอบ
สมมติว่าเป้าหมายของคุณคือการมี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
ฉันเห็นกรณีการใช้งานสำหรับสิ่งนี้ที่คุณต้องการ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ทำให้เกิดความขัดแย้งประเภท!
สิ่งนี้เกี่ยวข้องกับการทำซ้ำเล็กน้อยในการประกาศสองอินสแตนซ์ของทุกออบเจ็กต์ตามตัวอักษร แต่เป็นวิธีเดียวของ
- การมี TypeScript จะรู้ว่าช่องใดที่มีอยู่บนวัตถุรวมถึงวัตถุย่อยทั้งหมดในขณะที่ยังอยู่
- ตรวจสอบว่าค่าประเภท "ทั่วไป" ตรงกับข้อ จำกัด ของคุณ
คุณสามารถเล่นรอบด้วยวิธีนี้ในสนามเด็กเล่น
ใช้ฟังก์ชันยูทิลิตี้ทั่วไป ( 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
}
});