TypeScript 条件类型扩展 keyof,但仍然无法用于索引类型。

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

TypeScript conditional type extends keyof but still cannot be used to index type

问题

I wrote custom types in TypeScript using the template literal types to dynamically type the getters / setters of fields. The types look like this (Playground Link):

export type Getters<T> = {
  [K in `get${Capitalize<keyof T & string>}`]: K extends `get${infer S}` ? (
    S extends keyof T ? () => T[S] : (
      Uncapitalize<S> extends keyof T ? () => T[Uncapitalize<S>] : never
    )
  ) : never;
};

export type Setters<T> = {
  [K in `set${Capitalize<keyof T & string>}`]: K extends `set${infer S}` ? (
    S extends keyof T ? (newValue: T[S]) => void : (
      Uncapitalize<S> extends keyof T ? (newValue: T[Uncapitalize<S>]) => void : never
    )
  ) : never;
};

However, I face a type error line 12 (highlighted above), in the Setters<T> type definition, saying:

Type 'Uncapitalize' cannot be used to index type 'T'.

I'm a bit confused because I checked that Uncapitalize<S> extends keyof T just before, and while I might understand why there still is a type error (I don't know but maybe generic types can be different and two calls to Something<T> in a row might return a different type?), what really surprises me is that it worked a few lines above, in the Getters<T> type definition... The main question that comes to my mind now is: What is the difference between them? Why is one throwing a type error and not the other?

EDIT (I forgot to mention it the first time): I am aware that I can duplicate the type check inside the type of the parameter (as mentioned by @AntonKastritskiy in his answer), but I would like to avoid this repetition, and furthermore understand what happens here.

By the way, maybe there is a better way to do this (especially to keep a reference to the current keyof T being iterated, rather than needing to "deconstruct" the generated getter/setter name), but I tried my best to do it alone, as a kind of "exercise," but now I'm open to other improvement suggestions, if any. 😊

英文:

I wrote custom types in TypeScript using the template literal types to dynamically type the getters / setters of fields. The types look like this (Playground Link):

export type Getters&lt;T&gt; = {
  [K in `get${Capitalize&lt;keyof T &amp; string&gt;}`]: K extends `get${infer S}` ? (
    S extends keyof T ? () =&gt; T[S] : (
      Uncapitalize&lt;S&gt; extends keyof T ? () =&gt; T[Uncapitalize&lt;S&gt;] : never
    )
  ) : never;
};

export type Setters&lt;T&gt; = {
  [K in `set${Capitalize&lt;keyof T &amp; string&gt;}`]: K extends `set${infer S}` ? (
    S extends keyof T ? (newValue: T[S]) =&gt; void : (
      Uncapitalize&lt;S&gt; extends keyof T ? (newValue: T[Uncapitalize&lt;S&gt;]) =&gt; void : never
//                                                 ^^^^^^^^^^^^^^^^^^
    )
  ) : never;
};

However, I face a type error line 12 (highlighted above), in the Setters&lt;T&gt; type definition, saying:

> Type 'Uncapitalize<S>' cannot be used to index type 'T'.

I'm a bit confused because I checked that Uncapitalize&lt;S&gt; extends keyof T just before, and while I might understand why there still is a type error (I don't know but maybe generic types can be different and two calls to Something&lt;T&gt; in a row might return a different type?), what really surprise me is that it worked a few lines above, in the Getters&lt;T&gt; type definition... The main question that come to my mind now is: What is the difference between them? Why is one throwing a type error and not the other?

EDIT (I forgot to mention it the first time): I am aware that I can duplicate the type check inside the type of the parameter (as mentioned by @AntonKastritskiy in his answer), but I would like to avoid this repetition, and furthermore understand what happens here


By the way, maybe there is a better way to do this (especially to keep a reference to the current keyof T being iterated, rather than needing to "deconstruct" the generated getter/setter name), but I tried my best to do it alone, as a kind of "exercise", but now I'm open to other improvement suggestion, if any 😊

答案1

得分: 1

I do not know the exact answer to your question. The error only seems to reproduce when the type is used for a parameter but not the return type. You can remove the error by adding one more check to ensure that Uncapitalised<T> is a keyof T in the parameters, ie

export type Setters<T> = {
  [K in `set${Capitalize<keyof T & string>}`]: K extends `set${infer S}` ? (
    S extends keyof T ? (newValue: T[S]) => void : (
      Uncapitalize<S> extends keyof T ? (newValue: Uncapitalize<S> extends keyof T ? T[Uncapitalize<S>] : never) => void : never
    )
  ) : never;
};

PS: I want to point out that even though the code tries to handle a case when a field can start with an uppercase letter. This still won't handle a case when two cases differ by the casing of their first letter. For example:

type Foo = Setter<{a: number, A: string}> // {setA: string}
英文:

I do not know the exact answer to your question. The error only seem to reproduce when the type is used for a parameter but not the return type. You can remove the error by adding one more check to ensure that Uncapitalised<T> is a keyof T in the parameters, ie

export type Setters&lt;T&gt; = {
  [K in `set${Capitalize&lt;keyof T &amp; string&gt;}`]: K extends `set${infer S}` ? (
    S extends keyof T ? (newValue: T[S]) =&gt; void : (
      Uncapitalize&lt;S&gt; extends keyof T ? (newValue: Uncapitalize&lt;S&gt; extends keyof T ? T[Uncapitalize&lt;S&gt;] : never) =&gt; void : never
    )
  ) : never;
};

PS: I want to point out that even though the code tries to handle a case when a field can start with an uppercase letter. This still won't handle a case when two cases differ by the casing of their first letter. For example:

type Foo = Setter&lt;{a: number, A: string}&gt; // {setA: string}

huangapple
  • 本文由 发表于 2023年2月24日 05:49:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/75550666.html
匿名

发表评论

匿名网友

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

确定