I'm working on the following type definitions:
type NumericKeys<T> = {
[K in keyof T]: T[K] extends number ? K : never
}[keyof T]
type Update<T extends Record<string, any>> = {
$set?: {
[K in keyof T]?: T[K]
},
$inc?: Partial<Pick<T, NumericKeys<T>>>
}
Things are pretty close to the way that I want them except that in the case of both $set
and $inc
, these types allow a user to provide an explicit undefined value to any of the nested properties. Ideally all the properties in both objects would be optional, but if the user provides them they must match the type of the corresponding property from the original object.
type Item = {
name: string;
description: string;
count: number
}
const example: Update<Item> = {
$set: {
// Ideally this would throw a type error because it should be type string
name: undefined
},
$inc: {
// Ideally this would throw a type error because it should be type number
count: undefined
}
}
CodePudding user response:
TypeScript doesn't consistently distinguish missing from present-but-undefined
, as discussed in microsoft/TypeScript#13195 and issues linked therein. My general advice around this is that you probably shouldn't write code where something important hinges on such a distinction.
However, TypeScript 4.4 introduced the --exactOptionalPropertyTypes
compiler option which, when enabled, will not let you write undefined
to an optional property unless that property explicitly includes undefined
. For example:
// --exactOptionalPropertyTypes is enabled
type Foo = { a?: string };
let foo: Foo;
foo = {} // okay
foo = { a: undefined } // error!
// Type 'undefined' is not assignable to type 'string'
foo = { a: "hello" } // okay
Note that you can still read undefined
, since that's what you get if the property is missing:
const fooA = foo.a;
// const fooA: string | undefined
So if you enable --exactOptionalPropertyTypes
you can get the behavior you're looking for here. But note that there's a lot of existing code that doesn't make this distinction, and if you have some of this code in your code base then you will start seeing errors there too. This compiler option was intentionally not added to the --strict
suite of compiler features because of its potential for breaking a lot of real world code and harming productivity. See microsoft/TypeScript#44421 for a discussion on this; it was deemed "too invasive" to be bundled with --strict
.