网站搜索

将一个对象结构的高级 TypeScript 推断到另一个对象结构


情景

我有一个对象,它定义了一些需要解析的数据的配置。该对象包含类以及这些类上存在的方法的字符串名称

{
  orders: {
    service: OrderService, // The class
    serviceMethod: 'getOrders' // The method name which exists on the class
  },
  cart: {
    service: OrderService,
    serviceMethod: 'getCart'
  }
}

这个类看起来像这样

export class OrderService {
  public getOrders = (): Observable<Order[]> = {
    return of([ {id: '1', name: 'Order 1' ])
  };

  public getCart = (): Observable<Cart> => {
    return of({ products: [] })
  };
}

一旦配置被执行,解析后的数据如下所示:

{
  orders: [ {id: '1', name: 'Order 1' ], // Data of type Order[]
  cart: { products: [] } // Data of type Cart
}

问题

我正在尝试根据解析器配置自动推断已解析数据的类型。我有这个类型,但即使 T[P]['serviceMethod'] extends keyof T[P]['service'] 应该确保 T[P]['serviceMethod'] 是提供类的一个键,它仍然无法识别它

这是该问题的 StackBlitz 链接 https://stackblitz.com/edit/stackblitz-starters-9yqbuq?file=src%2Fapp%2Fapp.interface.ts

如果有人能修好这个,我会非常感激 :)

export interface ResolverResource<TService = any> {
   service: Type<TService>;
   serviceMethod: keyof TService;
}

export type ResolvedData<T, TService = any> = {
   // Check if T[P] is a ResolverResource
   [P in keyof T]: T[P] extends ResolverResource<TService>

      // Check if T[P]['serviceMethod'] exists on T[P]['service']
      ? T[P]['serviceMethod'] extends keyof T[P]['service']

         // Check if T[P]['service'][T[P]['serviceMethod']] is a function that returns an Observable
         // and infer the output type of the Observable
         ? T[P]['service'][T[P]['serviceMethod']] extends (...args: any[]) => Observable<infer U>
            ? U
            : never

         : never // Here is where it fails. It doesn't recognize that T[P]['serviceMethod'] is a key of T[P]['service']

      : never;
};

答案#1

开始觉得在 TypeScript 中使用 keyof 时,从 Observable 推断出显式类型存在限制。文档暗示它总是会输出所有键的联合类型。

执行以下操作可以让你的代码编译,但 orders 和 carts 属性都会互相联合。

export type ResolvedData<T, TService = any> = {
  [P in keyof T]: T[P] extends ResolverResource<TService>
    ?
      // You were missing this part, as 'keyof' Type<T> doesnt bring back the keys in the generic constraint
      T[P]['service'] extends Type<infer U>
        ?
          T[P]['serviceMethod'] extends keyof U
            ?
              U[T[P]['serviceMethod']] extends (
                ...args: any[]
              ) => Observable<infer V>
                ? V
                : never
            : never
        : never
    : never;
};

这会给你类似这样的东西:

const resolvedData: ResolvedData<typeof resourceConfig> = {
  orders: [],
  cart: { products: [] },
};

// resolvedData.orders == Cart | Order[]
// resolvedData.cart == Cart | Order[]