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

angular - เจนเนอริกของ Typescript ในคอลแบ็ก


ฉันกำลังพยายามสร้างบริการทั่วไปที่จะดึงข้อมูลระยะไกลมาบางส่วนและสร้างวัตถุบางอย่างจากมัน

@Injectable()
export class tService<T> {
    private _data: BehaviorSubject<T[]> = new BehaviorSubject([]);
    private url = '...url...';  // URL to web api

    constructor(private http: HttpClient) {    
        this.http.get<T[]>(this.url).subscribe(theThings => {
            theThings = _.map(theThings, (theThing) => new T(theThing));
            this._data.next(theThings);
        });
    }
}

นี่ทำให้ฉันได้รับข้อผิดพลาด T หมายถึงประเภทเท่านั้น แต่ถูกใช้เป็นค่าที่นี่ โอเค ไม่เป็นไร ฉันเข้าใจสิ่งที่เกิดขึ้นแล้ว ฉันเคยเห็นคำถามบางข้อที่ถามเกี่ยวกับสิ่งที่คล้ายกัน

ตัวอย่างเช่น:

  • วิธีสร้างออบเจ็กต์ใหม่จากพารามิเตอร์ชนิดในคลาสเจนริกในไทพ์สคริปต์?
  • TypeScript คืนอ็อบเจ็กต์ที่สร้างจากเจนเนอริก
  • รับตัวสร้าง/อินสแตนซ์จากประเภททั่วไปใน TypeScript

ดูเหมือนว่าวิธีแก้ปัญหาใด ๆ ที่ฉันเจอมาจะเป็นการกำหนดคลาสแบบคงที่ในบางจุด หรือเพิ่มตัวสร้างคลาสของ T ที่ไหนสักแห่ง แต่ฉันไม่สามารถหาวิธีทำได้ ปัญหาของฉันคือคลาสที่ถูกสร้างขึ้นมีพารามิเตอร์ในตัวสร้าง และฉันกำลังใช้ระบบ DI ของ Angular ดังนั้นฉันจึงไม่สามารถ (???) เพิ่มพารามิเตอร์ไปยังตัวสร้างของบริการได้

constructor(private playersService: gService<Player>, private alliesService: gService<Ally>) { }

มีใครสามารถคิดออกไหมว่าฉันควรทำอะไรที่นี่? นอกจากนี้ ฉันขอแนะนำให้คำตอบไม่ใช่บางอย่างที่เป็น 'แฮ็ก' ถ้าเป็นไปได้ ถ้าเป็นการเลือกระหว่างการทำบางอย่างที่อ่านแทบไม่ออกกับการคัดลอกและวางบริการนั้นหลายครั้งแล้วเปลี่ยนว่ามันอ้างถึงคลาสอะไร ฉันจะเลือกตัวเลือกที่สอง

คำตอบ #1

ชื่อคลาสหมายถึงทั้งประเภทของคลาส แต่ยังหมายถึงคอนสตรัคเตอร์ของมันด้วย นั่นคือเหตุผลที่คุณสามารถเขียนได้ทั้ง let x: ClassName และ new ClassName() พารามิเตอร์เจนเนอริกเป็นเพียงประเภทเท่านั้น (คล้ายกับอินเทอร์เฟซตัวอย่างเช่น) นั่นคือเหตุผลที่คอมไพเลอร์แจ้งข้อผิดพลาดว่าคุณกำลังใช้มันเป็นค่า (ค่าที่คาดหวังคือฟังก์ชันคอนสตรัคเตอร์) สิ่งที่คุณต้องทำคือส่งพารามิเตอร์เพิ่มเติมซึ่งจะเป็นคอนสตรัคเตอร์สำหรับ T

export class tService<T> {
    private _data: BehaviorSubject<T[]> = new BehaviorSubject<T>([]);
    private url = '...url...';  // URL to web api

    constructor(private http: HttpClient, ctor: new (data: Partial<T>)=> T) {    
        this.http.get<T[]>(this.url).subscribe(theThings => {
            theThings = _.map(theThings, (theThing) => new ctor(theThing));
            this._data.next(theThings);
        });
    }
}
//Usage
class MyClass {
    constructor(p: Partial<MyClass>) {
        // ...
    }
}
new tService(http, MyClass)

หมายเหตุ พารามิเตอร์สำหรับลายเซ็นต์ของคอนสตรัคเตอร์อาจแตกต่างกันไปตามกรณีการใช้งานของคุณ

แก้ไข

คุณได้กล่าวว่าคุณไม่สามารถเพิ่มอาร์กิวเมนต์ให้กับคอนสตรัคเตอร์ได้ generics (และทุกประเภทโดยทั่วไป) จะถูกลบออกเมื่อรันโค้ด ดังนั้นคุณจึงไม่สามารถพึ่งพา T ในเวลารันไทม์ได้ ที่ใดสักแห่ง จะต้องมีคนส่งคอนสตรัคเตอร์ของคลาสเข้ามา คุณสามารถทำได้หลายวิธี วิธีที่ง่ายที่สุดคือสร้างคลาสเฉพาะสำหรับแต่ละอินสแตนซ์ของ T แล้วจึงฉีดคลาสที่เฉพาะนั้น:

class tServiceForMyClass extends tService<MyClass> {
    constructor(http: HttpClient)  {
        super(http, MyClass)
    }
} 

หรือคุณสามารถทำงานของคุณขึ้นอยู่กับคอนสตรัคเตอร์ ในเมธอด init ที่ต้องการคอนสตรัคเตอร์เป็นพารามิเตอร์:

export class tService<T> {
    private _data: BehaviorSubject<T[]> = new BehaviorSubject<T>();
    private url = '...url...';  // URL to web api

    constructor(private http: HttpClient){}
    init(ctor: new (data: Partial<T>)=> T) {    
        this.http.get<T[]>(this.url).subscribe(theThings => {
            theThings = _.map(theThings, (theThing) => new ctor(theThing));
            this._data.next(theThings);
        });
    }
} 

บทความที่เกี่ยวข้อง