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

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

มักจะมีตัวเลือกในการกำหนดอินเทอร์เฟซเพิ่มเติมที่มีเฉพาะพร็อพเพอร์ตีอย่างชัดเจน แต่สิ่งนี้จะเพิ่มความยุ่งยากในการบำรุงรักษา