Issue
Referencing the snippet below, I would like to be able to call f
only with an object having a single property. Typescript should complain about additional properties, even when set to undefined
.
interface A {
a: number;
b?: never;
}
interface B {
a?: never;
b: number;
}
const f = (arg: A | B) => {};
// OK
f({ a: 0 });
// Error: Type 'number' is not assignable to type 'undefined'.
f({ a: 0, b: 0 });
// OK, but I would like to have an error
f({ a: 0, b: undefined });
In my case f
is a useReducer dispatcher which if called with { a: 0, b: undefined }
would set the property b
of my component state to undefined
, while if called with { a: 0 }
would leave the property b
alone.
There is no reason why I would ever dispatch an { a: 0, b: undefined }
action, but still, is there a way to have TypeScript covering my shoulders?
Solution
It is true that the default behavior is to treat optional properties as if a missing property is the same as a present property whose value is undefined
. Indeed, the compiler will automatically add undefined
to the domain of an optional property:
type Foo = {
x?: string,
y: string
}
/* type Foo = {
x?: string | undefined;
y: string;
} */
const foo: Foo = { y: "x" };
foo.x // string | undefined
foo.x = undefined; // okay
This is mostly fine for reading properties, since indeed get an undefined
value from a missing property. But for writing properties, this is less optimal, as it allows undefined
where most people wouldn't expect it to be allowed.
If you really don't like this, you could enable the --exactOptionalPropertyTypes
compiler option. If you do so, undefined
will no longer be added to the domain of an optional property, although you will still get a possibly undefined
value when you read such a property:
type Foo = {
x?: string,
y: string
}
/* type Foo = {
x?: string;
y: string;
}*/
const foo: Foo = { y: "x" };
foo.x // string | undefined
foo.x = undefined; // error
And you'll get the desired behavior for the code in your example:
// This code is from the example in the question
f({ a: 0, b: undefined }); // error!
So that's the answer to the question as asked. You should be aware, though, that the TS team intentionally excluded --exactOptionalPropertyTypes
from the "standard" --strict
suite of compiler options. That's because if you enable this option by default, it would be a large breaking change in a lot of code. Maybe if this feature had been introduced earlier, it would be fine. But now there is plenty of existing code that also doesn't distinguish between undefined
and missing properties very well:
const bar: Foo = { // error if flag enabled
x: foo.x,
y: foo.y
}
And so, depending on the rest of your code base and its dependencies, you may find it hard to just enable the feature.
Playground link to code, --exactOptionalPropertyTypes
off
Playground link to code, --exactOptionalPropertytypes
on
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.