There isn't a specific TypeScript type that precisely matches JavaScript objects containing at least one function-valued property.
Index signatures don't fit the criteria due to their lack of requiring properties (an empty object {}
can still comply with {[k: string]: Function}
) and their strict property type checks. Therefore, utilizing index signatures may not be effective in this scenario.
A potential alternative is to implement a generic constraint, specifically with an F-bounded polymorphism approach. This method involves having T extends TypeAtLeastRequired<T>
, where T
must adhere to certain constraints determined by the TypeAtLeastRequired
type function applied to it.
An example implementation could look like:
type TypeAtLeastRequired<T> = (
{ [K in keyof T]-?: T[K] extends Function ? unknown : never }[keyof T]
) extends never ? { "please add at least one function prop": Function } : T
If T
lacks any function-type properties, the mapped type
{[K in keyof T]-?: T[K] extends Function ? unknown : never}[keyof T]
will have all properties set as
the never
type. Conversely, if there's at least one function property present, a minimum of one non-
never
property will exist.
By applying a conditional type to the resulting unknown
or never
value against never
, we can identify whether T
adheres to the necessary requirement.
The expression T extends TypeAtLeastRequired<T>
establishes the sought-after constraint.
Now, let's put this into action. If implementing it within an implements
clause, repetition occurs as the implementing class name must be mentioned twice. Despite being repetitive, this practice is common in programming languages like Java:
class Foo implements TypeAtLeastRequired<Foo> { // error
msg: string = 'Hello!';
}
class Bar implements TypeAtLeastRequired<Bar> {
msg: string = 'Hello!';
okay() { }
}
If working with object literals, defining a type for verification purposes might seem cumbersome. Utilizing a generic helper function to infer the type can provide a solution:
const typeAtLeastRequired = <T,>(t: TypeAtLeastRequired<T>) => t;
Instead of declaring
const v: TypeAtLeastRequired = {...}
, you can use
const v = typeAtLeastRequired({...})
:
const okay = typeAtLeastRequired({
a: 0,
b: () => 2
})
const notOkay = typeAtLeastRequired({
a: 0, // error!
b: 2
})
Access the code on the Playground