Reputation: 181
How can I describe type like
key is optional, but if it existed, it cannot be undefined.
At the first time, I'm using Partial to make all properties of T optional.
interface A {
a: number;
}
var data: A = {
a: 1,
}
var update1: Partial<A> = {
}
var update2: Partial<A> = {
a: undefined
}
var result1: A = {
...data,
...update1,
} // { a: 1 }
var result2: A = {
...data,
...update2,
} // { a: undefined }
The problem here is that result2 is not implementing interface A in runtime, but typescript never complains about it. Is it bug or feature? I guess typescript not working well with spread operator...
The goal is distinguish these two variable using typescript!
var data: T = {
a: 1,
b: 2,
}
var optional1: MyPartial<T> = { // Totally OK
}
var optional2: MyPartial<T> = { // OK
a: 3,
}
var optional3: MyPartial<T> = { // OK
b: 4,
}
var optional4: MyPartial<T> = { // OK
a: 3,
b: 4,
}
var undef1: MyPartial<T> = { // typescript must throw error
a: undefined,
}
var undef2: MyPartial<T> = { // typescript must throw error
a: 3,
b: undefined
}
...
Check this out TypeScript playground example.
Upvotes: 18
Views: 7020
Reputation: 3449
Support now exists in 4.4.0 via --exactOptionalPropertyTypes
So by default, TypeScript doesn’t distinguish between a present property with the value undefined and a missing property ...
In TypeScript 4.4, the new flag --exactOptionalPropertyTypes specifies that optional property types should be interpreted exactly as written, meaning that | undefined is not added to the type:
https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#exact-optional-property-types
Upvotes: 13
Reputation: 2747
There is an error in Dmitriy answer, here is the fixed version
interface A {
a: number;
b: boolean
}
type PartialWithoutUndefined<M,T> = M extends T
? undefined extends M[keyof M]
? never
: M
: never
declare function update<M>(propsToUpdate: PartialWithoutUndefined<M,Partial<A>>): void
update({}) // OK
update({ a: 42 }) // OK
update({ a: undefined }) // error
update({ c: undefined }) // error
weakness: it is impossible to type anything as undefined even if that is your intention
Upvotes: 0
Reputation: 2822
As @dtanabe mentioned, there isn't really a good solution here until TypeScript support (if they decide to). And main problem from doing it in user land is that in a var def (i.e. const x: Foo = ...
) we do not have access to the type of the value. If you are OK changing your code from using spread to using a function you can do something like this:
interface A {
a: number;
}
type EnsurePartial<TTarget, TUpdate extends keyof TTarget> =
undefined extends TTarget[TUpdate]
? never
: { [Key in TUpdate]: TTarget[Key] };
declare function update<T>(propsToUpdate: EnsurePartial<A, T>): void
update({}) // OK
update({ a: 42 }) // OK
update({ a: undefined }) // error
update({ c: undefined }) // error
Upvotes: 3