如何从函数参数中推导类型?

huangapple go评论71阅读模式
英文:

How can I derive types from function argument?

问题

以下是您要翻译的内容:

"I want a function createApi that will infer types based on the config passed in.

Here is what I have so far:

export type EndpointConfig<Input, Resp> = (http: Http) => RequestConfig<Input, Resp>;

export type RequestConfig<Input, Resp> = {
  query: (input: Input) => Promise<Resp>
}

export type ApiConfig<M extends EndpointMap> = {
  endpoints: { [K in keyof M]: EndpointConfig<M[K]['input'], M[K]['resp']> }
}

type EndpointMap = { [key: string]: { resp: any, input: any } };

export const createApi = <M extends EndpointMap>(config: ApiConfig<M>): ApiConfig<M> => {
  // omit implementation
  return config;
};

export interface Http {
  get: <T>(url: string) => Promise<T>
}

// ============ using createApi ============

interface User {
  username: string
}

export const api = createApi({
  endpoints: {
    users: http => ({
      // here is where the types should come from
      query: (input: string) => http.get<User>(`/users/${input}`),
    }),
  },
});

const { users } = api.endpoints;
// typeof users is EndpointConfig<any, any>
// but I want EndpointConfig<string, User>

希望这对您有所帮助。

英文:

I want a function createApi that will infer types based on the config passed in.

Here is what I have so far:

export type EndpointConfig&lt;Input, Resp&gt; = (http: Http) =&gt; RequestConfig&lt;Input, Resp&gt;;

export type RequestConfig&lt;Input, Resp&gt; = {
  query: (input: Input) =&gt; Promise&lt;Resp&gt;
}

export type ApiConfig&lt;M extends EndpointMap&gt; = {
  endpoints: { [K in keyof M]: EndpointConfig&lt;M[K][&#39;input&#39;], M[K][&#39;resp&#39;]&gt; }
}

type EndpointMap = { [key: string]: { resp: any, input: any } };

export const createApi = &lt;M extends EndpointMap&gt;(config: ApiConfig&lt;M&gt;): ApiConfig&lt;M&gt; =&gt; {
  // omit implementation
  return config;
};

export interface Http {
  get: &lt;T&gt;(url: string) =&gt; Promise&lt;T&gt;
}

// ============ using createApi ============

interface User {
  username: string
}

export const api = createApi({
  endpoints: {
    users: http =&gt; ({
      // here is where the types should come from
      query: (input: string) =&gt; http.get&lt;User&gt;(`/users/${input}`),
    }),
  },
});

const { users } = api.endpoints;
// typeof users is EndpointConfig&lt;any, any&gt;
// but I want EndpointConfig&lt;string, User&gt;

答案1

得分: 1

给定一个类型值

type ApiConfig<M extends EndpointMap> = {
  endpoints: { [K in keyof M]: EndpointConfig<M[K]['input'], M[K]['resp']> }
}

对于某个 M,编译器实际上无法从中正确推断出 M。要做到这一点,需要能够从任意的索引访问类型(比如 T['input']T['resp'])中合成类型,但 TypeScript 目前还不能做到这一点。有一个相关的功能请求,可以在 microsoft/TypeScript#51612 中找到,但目前还不包括在语言中。


在这个功能得到实现之前,你需要绕过它。更容易的做法是推断一个类型参数,使其正好等于传入函数参数的类型,然后从中计算出所需的类型。所以,不要使用 config: ApiConfig<M>,而是使用 config: {endpoints: E},然后返回 E 或从 E 计算出的某种类型。例如:

export const createApi = <
  E extends Record<keyof E, EndpointConfig<any, any>>
>(config: { endpoints: E }): {
  endpoints: { [K in keyof E]:
    E[K] extends EndpointConfig<infer I, infer R> ?
    EndpointConfig<I, R> : E[K]
  }
} => {
  // 忽略实现
  return config;
};

现在当我们测试它时:

export const api = createApi({
  endpoints: {
    users: http => ({
      query: (input: string) => http.get<User>(`/users/${input}`),
    }),
  },
});

/* const api: {
    endpoints: {
        users: EndpointConfig<string, User>;
    };
} */

const { users } = api.endpoints;
// const users: EndpointConfig<string, User>

你会得到你所期望的行为。

Playground 链接到代码

英文:

Given a value of type

type ApiConfig&lt;M extends EndpointMap&gt; = {
  endpoints: { [K in keyof M]: EndpointConfig&lt;M[K][&#39;input&#39;], M[K][&#39;resp&#39;]&gt; }
}

for some M, the compiler really can't properly infer M from it. To do so would require that types could be synthesized from arbitrary indexed access types like T[&#39;input&#39;] or T[&#39;resp&#39;], but TypeScript doesn't currently do that. There's an open feature request for it at microsoft/TypeScript#51612, but for now it's not part of the language.


Until an unless that gets implemented you'll need to work around it. It's a lot easier to infer a type parameter to be exactly the type of the passed in function argument, and then evaluate your desired type from that. So instead of config: ApiConfig&lt;M&gt;, you'd have config: {endpoints: E} and then return either E or some type that is computed from E. For example:

export const createApi = &lt;
  E extends Record&lt;keyof E, EndpointConfig&lt;any, any&gt;&gt;
&gt;(config: { endpoints: E }): {
  endpoints: { [K in keyof E]:
    E[K] extends EndpointConfig&lt;infer I, infer R&gt; ?
    EndpointConfig&lt;I, R&gt; : E[K]
  }
} =&gt; {
  // omit implementation
  return config;
};

And now when we test it:

export const api = createApi({
  endpoints: {
    users: http =&gt; ({
      query: (input: string) =&gt; http.get&lt;User&gt;(`/users/${input}`),
    }),
  },
});

/* const api: {
    endpoints: {
        users: EndpointConfig&lt;string, User&gt;;
    };
} */

const { users } = api.endpoints;
// const users: EndpointConfig&lt;string, User&gt;

You get the behavior you're looking for.

Playground link to code

huangapple
  • 本文由 发表于 2023年7月7日 06:01:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632766.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定