Home > Software engineering >  typescript 'keyof' change the origin property optional?
typescript 'keyof' change the origin property optional?

Time:10-12

Define an interface like this

interface Props {
  a?: string
  b?: string
  c?: string
  d: string
}

When I do something like this, it is ok

type Test1 = {
  [P in keyof Props]: Props[P]
}

const a1: Test1 = {
  d: '1'
}

but, like this, I got some error message

type Keys = keyof Props;

type Test1 = {
  [P in Keys]: Props[P]
}

// error Type '{ d: string; }' is missing the following properties from type 'Test1': a, b, c(2739)

const a1: Test1 = {
  d: '1'
}

Another one

type MyOmit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }

// Property 'a' is missing in type '{ d: string; }' but required in type 'MyOmit<Props, "b" | "c">'.
const o1: MyOmit<Props, 'b' | 'c'> = {
  d: '1'
}

// Its Ok!
const o2: Omit<Props, 'b' | 'c'> = {
  d: '1'
}

Why MyOmit get error?

CodePudding user response:

There is a huge difference between type Test1 = { [P in keyof Props]: Props[P] } and type Test2 = { [P in Keys]: Props[P] }.

See first one: enter image description here

Each property is optional.

And second one: enter image description here

Each property is required but might be undefined.

Please see this flag.

The reason why TS used this approach is dynamic nature of JS.

Consider this example:

const foo = { name: undefined }
foo.name // undefined
foo.surname // undefined

JS returns undefined if you are trying to get property which does not exists. You can find more explanation in ts docs.

While this flag does not affect the behavior of your example - it gives you nice official explanation of the error you are getting.

If you want to make it work, you just need to make Test2 partial:

type Test2 = { [P in Keys]?: Props[P] } // <---- added question mark

OR:

type Test2 = Partial<{ [P in Keys]: Props[P] }>

Why keyof Props is not the same as Keys?

I did not take into account this behavior while answering this question.

Seems to be that in Test1 - keyof Props does not create separate intermediate union type for Props keys and iterates directly through Props interface keys taking into account all modificators, whereas during iteration in Test2, TS iterates through union of chars: a | b | c | d and knows nothing about Props.

I have encounter similar behavior when I was working with enums:


enum MyEnum {
    ONE,
    TWO
}

type Enumerate<Enum extends number | string> = keyof {
    [Prop in Enum]: Prop
}

type Result2 = Enumerate<MyEnum>

Enumerate should return keys of MyEnum which is a union of ONE | TWO but it returns MyEnum

CodePudding user response:

This is the source of Omit

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

So that's what's the difference here–keyof grabs all keys and denotes them as non-optional and constructs an object, whereas Pick constructs the object from the original, T with optional declaration persisted.

  • Related