Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

typescript - Using Omit type on classes with a index type signature results in minimum properties not being required

I have a class which has some mandatory properties and can then have other properties defined by classes extending this one (or anyway, some extra properties I don't know about).

Now I'm trying to use Omit type on it, defined as

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

If we define the class as

class Thing {
  constructor(
     public id: number,
     public name: string,
     public description: string
  ) {}
}

We can do Omit<Thing, 'id'> and get the right type inference as Pick<Thing, "name" | "description">

If we add a [index: string]: any to the class to support unknown properties and retry Omit<Thing, 'id'>, now the inferred type will be Pick<Thing, string | number>.

class Thing {
  constructor(
     public id: number,
     public name: string,
     public description: string
  ) {}

  [index: string]: any;
}

This doesn't really make sense: when I type using the class, all properties are required even when a [index: string]: any; is defined, why would it become a looser type when omitting one of its properties? I would expect the return type to still be Pick<Thing, "name" | "description">.

Does anyone knows what's going on?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

UPDATE FOR TYPESCRIPT 4.1+

Since typeScript 4.1 introduced key remapping in mapped types, you can now write a non-ugly version of Omit which handles types with index signatures more gracefully:

type NewOmit<T, K extends PropertyKey> = 
  { [P in keyof T as Exclude<P, K>]: T[P] };

You can verify that it works as desired:

type RemoveId = NewOmit<Thing, "id">;
/* type RemoveId = {
    [x: string]: any;
    name: string;
    description: string;
} */

Nice!

Playground link to code


PRE-TYPESCRIPT 4.1 ANSWER

You can make an Omit<> that handles index signatures, but it's really ugly.

As noted, keyof T if T has a string index signature is string | number, and any literal keys (like 'id') get eaten up. It is possible to get the literal keys of a type with an index signature, by doing some crazy type-system hoop jumping. Once you have the literal keys and the index signature keys, you can build up an Omit:

type _LiteralKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
type LiteralKeys<T> = _LiteralKeys<T> extends infer K ?
  [K] extends [keyof T] ? K : never : never;

type Lookup<T, K> = K extends keyof T ? T[K] : never;
type _IndexKey<T> = string extends keyof T ?
  Lookup<T, string> extends Lookup<T, number> ?
  string : (string | number) : number extends keyof T ? number : never;
type IndexKey<T> = _IndexKey<T> extends infer K ?
  [K] extends [keyof T] ? K : never : never;

type WidenStringIndex<T extends keyof any> =
  string extends T ? (string | number) : T;

type Omit<T, K extends keyof T,
  O = Pick<T, Exclude<
    LiteralKeys<T>, K>> & Pick<T, Exclude<IndexKey<T>, WidenStringIndex<K>>>
  > = { [P in keyof O]: O[P] }

I said it was ugly.

You can verify that it behaves as you expected:

type RemoveId = Omit<Thing, 'id'>;
// type RemoveId = {
//  [x: string]: any;
//  name: string;
//  description: string;
// }

If you want to take the above Omit, shove it in some library where nobody has to look at it, and use it in your code; that's up to you. I don't know that it handles all possible edge cases, or even what the "right" behavior is in some instances (e.g., you can have both a string and a number index where the number values are narrower than the string values... what do you want to see if you Omit<T, string> on such a type? ???♂?) So proceed at your own risk. But I just wanted to note that a solution of sorts is possible.

Okay, hope that helps. Good luck!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...