简体   繁体   English

使用 generics 获取嵌套类型

[英]Get nested type with generics

I have an object of services, each with nested requests.我有一个 object 服务,每个都有嵌套请求。 I'm trying to create a function to get a specific request, and want the correct type on the output.我正在尝试创建一个 function 来获取特定请求,并希望 output 上的类型正确。

Consider the following code:考虑以下代码:

enum Service {
  Foo = 'foo',
  Bar = 'bar'
}

const services = {
  [Service.Foo]: {
    v1: {
      getGreeting: (id: string) => 'hello',
    },
  },
  [Service.Bar]: {
    v2: {
      getInsult: () => 'idiot',
    },
  },
};

function createRequest<T extends Service>(
  serviceName: T,
  version: string,
  endpoint: string,
) {
  const service = services[serviceName] as typeof services[T];
  return service[version][endpoint];
}

const request = createRequest(Service.Foo, 'v1', 'getGreeting');

I'm nearly there but request has the type any .我快到了,但request的类型是any The function already knows the type of the service via generics, but how do I go the extra mile to get the type of the nest property being returned? function 已经通过 generics 知道服务的类型,但是我如何通过 go 获得额外的英里来获得返回的嵌套属性的类型?

Let's create an interface for service data structure:让我们为服务数据结构创建一个接口:

type ServiceType = Record<Service, Record<string, Record<string, (...args: any[]) => any>>>;

Now, in order to infer all arguments and validate them we need to make this function pure .现在,为了推断所有 arguments 并验证它们,我们需要将此 function 设为pure TS likes pure functions more. TS 更喜欢pure函数。 We should create a function which will return another function ( curry it)我们应该创建一个 function 它将返回另一个 function ( curry它)

const withService = <
  ServiceObj extends ServiceType
>(services: ServiceObj) =>
  <
    Name extends keyof ServiceObj,
    Version extends keyof ServiceObj[Name],
    Endpoint extends keyof ServiceObj[Name][Version]
  >(
    serviceName: Name,
    version: Version,
    endpoint: Endpoint,
  ) => services[serviceName][version][endpoint];

Now we can test it:现在我们可以测试它:

const request = withService({
  [Service.Foo]: {
    v1: {
      getGreeting: (id: string) => 'hello',
    },
  },
  [Service.Bar]: {
    v2: {
      getInsult: () => 'idiot',
    },
  },
})

// (id: string) => string
const createRquest = request(Service.Foo, 'v1', 'getGreeting')

// () => string
const createRquest2 = request(Service.Bar, 'v2', 'getInsult')

const createRquest3 = request(Service.Bar, 'v22', 'getInsult') // expected error

Playground操场

As you might have noticed keyof ServiceObj[Name][Version] reflects function body logic您可能已经注意到keyof ServiceObj[Name][Version]反映了 function 主体逻辑

If you are interested in function argument inference, you can check my related article如果你对 function 参数推理感兴趣,可以查看我的相关文章


Here you have a version with classes:在这里,您有一个带有类的版本:

enum Service {
  Foo = 'foo',
  Bar = 'bar'
}

interface Base {
  [prop: `v${number}`]: Record<string, Fn>
}

class Foo {
  v1 = {
    getGreeting: (id: string) => 2,
  }
}

class Bar {
  v2 = {
    getInsult: () => 'idiot',
  }
}

type WithoutIndex = keyof Bar
const services = {
  [Service.Foo]: Foo,
  [Service.Bar]: Bar,
}

type AnyClass = new (...args: any[]) => Base
type Fn = (...args: any[]) => any


function withService<
  ServiceObj extends Record<Service, new (...args: any[]) => any>,
  >(services: ServiceObj):
  <Name extends Service,
    Version extends keyof InstanceType<ServiceObj[Name]>,
    Endpoint extends keyof InstanceType<ServiceObj[Name]>[Version]
    >(
    serviceName: Name,
    version: Version,
    endpoint: Endpoint,
    v?: keyof InstanceType<ServiceObj[Name]>
  ) => InstanceType<ServiceObj[Name]>[Version][Endpoint]
function withService<
  ServiceObj extends Record<Service, AnyClass>,
  >(services: ServiceObj) {
  return <Name extends Service,
    Version extends keyof Base,
    Endpoint extends keyof Base[Version]
  >(
    serviceName: Name,
    version: Version,
    endpoint: Endpoint,
  ) => {
    const api = new services[serviceName]();
    return api[version][endpoint];
  }
};


const request = withService({
  [Service.Foo]: Foo,
  [Service.Bar]: Bar,
})

// (id: string) => number
const createRquest = request(Service.Foo, 'v1', 'getGreeting')

// () => string
const createRquest2 = request(Service.Bar, 'v2', 'getInsult')

const createRquest3 = request(Service.Bar, 'v22', 'getInsult') // expected error

Playground操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM