【问题标题】:Typescript generics in a callback回调中的打字稿泛型
【发布时间】:2018-04-05 05:41:57
【问题描述】:

我正在尝试创建一个通用服务,该服务将获取一些远程数据并使用它创建一些对象。

@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 only refers to type, but is being used as a value here。好没问题。我明白发生了什么。我看到有几个问题询问类似的问题。

例如:

似乎我遇到的任何解决方案要么在某个时候在类中使用硬编码,要么在某处添加 T 的类构造函数。但我不知道该怎么做。我的问题是被实例化的类在构造函数中有参数,而我使用的是 Angular 的 DI 系统,所以我不能(???)向服务的构造函数添加参数。

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

谁能弄清楚我应该在这里做什么?另外,如果可能的话,我希望答案不是某种“黑客”。如果它在做一些几乎不可读的事情和只是复制和粘贴服务几次和更改它所指的类之间做出选择,我会采取第二种选择。

【问题讨论】:

  • 传递构造函数 is 是正确的方法,但你说得对,用 Angular 的 DI 抽象来做这件事很尴尬(尽管很有可能)。您是否考虑过用泛型方法替换泛型类?
  • @AluanHaddad 不是真的,那会是什么样子?
  • 点赞class S { get&lt;T&gt;(C: new (x: Thing) =&gt; T): Promise&lt;T&gt; {...} }。但是我现在看到您正在使用构造函数本身来触发异步进程的初始化,然后更改应用程序级别的状态。也就是说,您的服务正在作为被需要它的东西注入(并因此实例化)的服务的副作用......
  • @AluanHaddad 嗯,是的,可能不应该在构造函数中做异步的事情。从字里行间看,我认为您是在说我犯了几个导致我的问题的设计错误?
  • 嗯...是的,我建议您可能有。很难说,因为您可能有一个我没有考虑过的用例。令我恼火的是,如果为与它一起使用的每个不同类型参数创建了一个实例,则通用参数仅在服务类上才有意义。是单例还是应用中有多个实例?

标签: angular typescript generics


【解决方案1】:

类名既指类类型,也指它的构造函数,这就是为什么你可以写let x: ClassNamenew ClasName()。泛型参数只是一种类型(例如,很像接口),这就是为什么编译器会抱怨您将其用作值(预期的值是构造函数)。您需要做的是传递一个额外的参数,该参数将成为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)

注意构造函数签名的参数可能会根据您的用例而有所不同。

编辑

你提到你不能向构造函数添加参数,泛型(以及一般的所有类型)在我们运行代码时被删除,所以你不能在运行时依赖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);
        });
    }
} 

【讨论】:

  • 我正在使用 Angular DI。这没有。当我试图适应你在这里的东西时,它失败了。 Can't resolve all parameters for tService: ([object Object], ?).
  • @Shane 有人需要传入T 的构造函数。我不确定谁 Angular DI 处理泛型,但由于泛型只是一个编译类型构造,我认为他们只是创建一个新的 tService 而他们并不关心 T
  • @Shane 添加了一些选项,但您需要以某种方式传递T 的构造函数,打字稿将不知道如何处理new T
猜你喜欢
  • 2020-06-29
  • 2015-11-27
  • 2021-11-26
  • 1970-01-01
  • 2021-09-10
  • 2018-11-17
  • 2021-12-20
  • 2022-08-21
  • 1970-01-01
相关资源
最近更新 更多