typescript - การกำหนดอินเทอร์เฟซแบบไดนามิก
ฉันมีคลาสฐานนามธรรมสำหรับชุดของคลาส "การกำหนดโครงสร้าง":
xyzขยายBaseSchema
สิ่งที่ฉันต้องการให้แน่ใจคือว่าคลาสสกีมาแม่กำลังส่งออกข้อจำกัด/อินเทอร์เฟซที่มีความหมายสำหรับคุณสมบัติของสกีมา (และในที่สุดสำหรับความสัมพันธ์ของมัน) ดังนั้นตอนนี้คลาสฐานถูกกำหนดเป็น:
export abstract class BaseSchema {
/** The primary-key for the record */
@property public id?: string;
/** The last time that a given model was updated */
@property public lastUpdated?: datetime;
/** The datetime at which this record was first created */
@property public createdAt?: datetime;
/** Metadata properties of the given schema */
public META?: Partial<ISchemaOptions>;
public toString() {
const obj: IDictionary = {};
this.META.properties.map(p => {
obj[p.property] = (this as any)[p.property];
});
return JSON.stringify(obj);
}
public toJSON() {
return this.toString();
}
}
ตัวอย่างของคลาสพาเรนต์อาจมีลักษณะดังนี้:
export class Person extends BaseSchema {
// prettier-ignore
@property @length(20) public name: string;
@property public age?: number;
@property public gender?: "male" | "female" | "other";
// prettier-ignore
@property @pushKey public tags?: IDictionary<string>;
// prettier-ignore
@ownedBy(Person) @inverse("children") public motherId?: fk;
// prettier-ignore
@ownedBy(Person) @inverse("children") public fatherId?: fk;
@hasMany(Person) public children?: fk[];
@ownedBy(Company) public employerId?: fk;
}
ตอนนี้คุณจะสังเกตได้ว่าฉันกำลังใช้การสนับสนุน decorators ของ Typescript และในกรณีนี้ สิ่งสำคัญที่ควรเข้าใจเกี่ยวกับโค้ด decorator คือ @property decorator ถูกใช้เพื่อระบุคุณสมบัติอย่างชัดเจนในสคีมา ซึ่งจะบันทึกข้อมูลเมตาเกี่ยวกับคุณสมบัติของสคีมาไว้ในวัตถุพจนานุกรมบน META.proproperties ของคลาสสคีมา คุณจะเห็นว่ามันถูกใช้ภายหลังในเมธอด toString() หากคุณรู้สึกว่าโค้ด decorator ที่แท้จริงสำคัญต่อการเข้าใจ คุณสามารถหามันได้ที่นี่
สิ่งที่ฉัน อยาก สามารถทำได้คือการเปิดเผยการกำหนดอินเทอร์เฟซซึ่งจำกัดประเภทไปยังคุณสมบัติและประเภทของคุณสมบัติที่กำหนดในสกีมา อะไรบางอย่างประมาณดังนี้:
function foobar(person PersonProperties) { ... }
เพื่อให้คุณสมบัติ "person" ถูกจำกัดโดยคุณสมบัติที่จำเป็นและให้ intellisense ในตัวแก้ไขเสนอทั้งคุณสมบัติที่จำเป็น (เช่น "name") และคุณสมบัติที่ไม่จำเป็น (เช่น "age", "createdAt" เป็นต้น) ที่กำหนดเป็นคุณสมบัติ
ในที่สุดฉันอยากจะส่งออกประเภทที่ทำสิ่งเดียวกันสำหรับความสัมพันธ์ แต่ตอนนี้ฉันก็ยินดีแค่ได้คุณสมบัติของสกีมา
คำตอบ #1
ใน TypeScript 2.8 คุณสามารถใช้ประเภทเชิงเงื่อนไข (conditional types) และประเภทที่แมปแล้ว (mapped types) เพื่อสร้างประเภทที่มีเฉพาะฟิลด์ของคลาสเท่านั้น ไม่รวมเมธอด และยังคงรักษาคุณสมบัติที่จำเป็น/ไม่จำเป็นของฟิลด์ไว้ได้:
type NonMethodKeys<T> = {[P in keyof T]: T[P] extends Function ? never : P }[keyof T];
type RemoveMethods<T> = Pick<T, NonMethodKeys<T>>;
class Person {
public name: string;
public age?: number;
public gender?: "male" | "female" | "other";
toJson(): string { return ''}
}
let ok: RemoveMethods<Person> = {
name : 'Snow',
gender: 'male'
};
// Error no name
let nokNoName: RemoveMethods<Person> = {
};
let nok: RemoveMethods<Person> = {
name : 'Snow',
gender: 'male',
toJson(): string { return ''} // error unknown property
};
หากคุณกำลังใช้ Typescript 2.7 หรือต่ำกว่า จะไม่มี conditional types และคุณสามารถใช้ได้เพียง mapped type เช่น Pick หรือ Partial สำหรับการอภิปรายเกี่ยวกับข้อดีเปรียบเทียบของพวกมัน คุณสามารถดูคำถามนี้ได้
น่าเสียดายที่ไม่มีวิธีใดที่จะใช้ตัวตกแต่ง (decorators) เพื่อแยกแยะว่าฟิลด์ใดบ้างที่จะเข้าสู่ส่วนต่อประสาน เราสามารถใช้ได้เพียงประเภทของฟิลด์เท่านั้น คุณอาจพิจารณาการออกแบบที่คลาสของคุณมีเฉพาะฟิลด์ที่เป็นพร็อพเพอร์ตี้ด้วย หรือฟิลด์เหล่านั้นถูกเก็บไว้ในคลาสพิเศษที่มีเฉพาะพร็อพเพอร์ตี้เท่านั้น
มักจะมีตัวเลือกในการกำหนดอินเทอร์เฟซเพิ่มเติมที่มีเฉพาะพร็อพเพอร์ตีอย่างชัดเจน แต่สิ่งนี้จะเพิ่มความยุ่งยากในการบำรุงรักษา
