We know that currently TypeScript’s built-in Required
utility type is implemented by “removing the optional tag” using -?
like
type Required<T> = { [K in keyof T]-?: T[K] }
My question is that since an optional property is actually (maybe) created like | undefined
e.g.
interface Foo {
name?: string
}
where typeof Foo['name']
is string | undefined
, it’s natural that we can Exclude
the undefined
to implement Required
like
type MyRequired<T> = { [K in keyof T]: Exclude<T[K], undefined> }
However, actually this idea doesn’t work. The output type is the same as T
without any modification. Why doesn’t it work?
If a property is optional then the compiler automatically adds undefined
to its domain (unless --exactOptionalPropertyTypes
is enabled). That is, allowing undefined
is one of the effects of making a property optional.
But the direction of causality doesn’t go the other way. Adding undefined
to a property doesn’t make it optional, and removing (or trying to remove) undefined
from a property doesn’t make it required. So your approach doesn’t work because you’re trying to do it backwards.
A mapped type of the form {[K in keyof T]: ⋯}
is homomorphic (see What does “homomorphic mapped type” mean?) and automatically copies the “optionality” and “readonly
-ness” from the input type T
to the output type. So if a property of T
is optional, then the corresponding property of {[K in keyof T]: ⋯}
will also be optional. And removing undefined
from the output property will have no effect, because it will be added right back by the compiler.
If you want to prevent a homomorphic mapped type from preserving optionality, you can use the -?
mapping modifier to make all output properties required and it will also remove undefined
from the domain of the output property. You can also just prevent the mapped type from being recognized as homomorphic, if you want to make the property required but keep undefined
(see this comment on microsoft/TypeScript#31025).
Suppose type A = {x?: undefined | 1}
That includes all these instances
{}
{x:undefined}
{x:1}
Suppose type B = {x: undefined | 1}
That includes only these instances
{x:undefined}
{x:1}
Instances of MyRequired(A)
:
{}
{x:1}
which can be written as
type MyReqA = {x?:1}
Instances of MyRequired(B)
:
{x:1}
which can be written as
type MyReqB = {x:1}
So MyRequired doesn’t work for A, but it does for B. As expected.
Background that may or may not be useful:
In real life JS, a property key can have a value of undefined
.
a = {x: undefined}
or have the key not present
b = {}
and both return the same value trying to read the key:
a.x; // undefined
b.x; // undefined
So, are you looking for OmitOptional , ExcludeValueUndefined, or exactOptionalPropertyTypes in tsconfig (which removes implied undefined from optional props)?