Way to implement `Required` with `Exclude` instead of `-?`

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?

  • So, are you looking for OmitOptional , ExcludeValueUndefined, or exactOptionalPropertyTypes in tsconfig (which removes implied undefined from optional props)?

    – 

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

Leave a Comment