Reputation: 49754
Here's a simple type I compiled with TS4.1.3:
interface Foo {
[key: string]: any;
bar: 'baz';
}
This should allow any value to any property except for bar
, which is limited to a single value. So I can't do const x: Foo = { bar: 'asdfadsf' }
for example. This is exactly as I expected.
I then do this:
type Bar = Pick<Foo, string>;
Unless I misunderstand how Pick
works, this is supposed to be a no-op as all the keys are in string
, including bar
.
However to my surprise const x: Bar = { bar: 'asdfadsf' }
does compile.
Only if I change the definition of Bar
to Pick<Foo, string & 'bar'>;
does it become a true no-op.
Where am I going wrong?
P.s.: obviously this is a MWE, the actual scenario involves React and other third party libraries, and ultimately I can force the correct type signature, I just want to learn.
Upvotes: 1
Views: 245
Reputation: 42218
In order to understand this, you have to look at the definition of Pick
and what it is doing step by step.
This is the type that typescript is applying behind the scenes:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
So your type:
type Bar = Pick<Foo, string>;
is really:
type Bar = {
[P in string]: Foo[P];
}
Foo[string]
is any
even though Foo['bar']
is baz
.
There is only one signature here, so it gets simplified to a basic string index signature:
type Bar = {
[x: string]: any;
}
When you change it to Pick<Foo, string & 'bar'>
that is the same as Pick<Foo, 'bar'>
. This no longer matches the string index signature, so you get a type which just includes the defined key bar
:
type Bar = {
bar: 'baz';
}
Perhaps the truest no-op would be to Pick
the two signatures separately and combine them:
type Bar = Pick<Foo, 'bar'> & Pick<Foo, string>;
becomes:
type Bar = {
bar: 'baz';
} & {
[x: string]: any;
}
Upvotes: 2