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] }
.
Each property is optional.
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:
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.