Reputation: 2351
Is there a possiblity to define a function more typesafe than this?
public addBusinessRule(targetProperty: string,
dependentProperties: string[],
callback: (dep0: any, dep1: any, ...)): void {
// some logic...
callback(..dependentProperties)
};
my goal is to get typechecking for the last 2 arguments. so if I pass in something like this:
this.addBusinessRule('mortage',
['purchasePrice', 'investiture', 'ownFunds'],
(purchase: number, investiture: number, ownFunds: number) => {
// some calculations
});
anyone got an idea how i could define the size of the string-array from the second argument, that has to match the numbers of arguments accepted from the callback function.
can i somehow solve this with generics or is probably just no solution for this.
If someone knows a better more exact question title please request an edit!
Upvotes: 1
Views: 354
Reputation: 250016
You can use mapped tuples to transform the tuple of properties into a tuple of property types which can then be used as the arguments to the callback. This will ensure the callback can have at most the same number of parameters as items in dependentProperties
. It will not force you to specify all arguments (this is how typescript defines type compatibility for functions).
type Data = {
mortage: {
purchasePrice: number,
investiture: number,
ownFunds: number,
otherProp: string
}
}
type MapTuple<T, K extends keyof T, NK extends Array<keyof T[K]>> = {
[P in keyof NK]: NK[P] extends keyof T[K] ? T[K][NK[P]] : never
}
class Test {
public addBusinessRule<K extends keyof Data, NK extends Array<keyof Data[K]>>(targetProperty: K,
dependentProperties: NK | [], // Get the compiler to infer tuple types
callback: (...a: MapTuple<Data, K, NK>) => void): void {
// some logic...
//callback(..dependentProperties)
};
public m() {
this.addBusinessRule('mortage',
['purchasePrice', 'investiture', 'ownFunds', 'otherProp'],
(purchase, investiture, ownFunds, op) => { // param types infered based on Data typr
});
}
}
The magic happens in the MapTuple
type. The type uses mapped types (which since 3.1 support tuples and arrays as well, see PR). This type takes a the tuple NK
which is a tuple of keys of T[K]
, and looks up the type of each property in T[K]
(this involves a conditional type because typescript can't figure out NK[P]
is a key of T[K]
although it is guaranteed to be)
If you just want the number of parameters to be checked and not their type (although I would argue that is a worse experience for your API consumers) you can do the following:
type MapTuple<NK extends Array<any>> = {
[P in keyof NK]: any
}
class Test {
public addBusinessRule<NK extends Array<string>>(targetProperty: string,
dependentProperties: NK | [], // Get the compiler to infer tuple types
callback: (...a: MapTuple<NK>) => void): void {
// some logic...
//callback(..dependentProperties)
};
public m() {
this.addBusinessRule('mortage',
['purchasePrice', 'investiture', 'ownFunds', 'otherProp'],
(purchase, investiture, ownFunds, op) => { // all of type any
});
}
}
Upvotes: 2
Reputation: 94
What about a simpler example, just for string typed arguments?
export const addBusinessRule = <T extends any[]>(targetProperty: string, dependentProperties: T, callback: (...a: T[]) => void) => {
// some logic...
callback(...dependentProperties)
}
addBusinessRule('asd', ['sdf', 'sdf', 'sdf'], (first, second, third) => {
console.log('ABC')
})
It provides types for each argument.
And if we're gonna use incorrect callback, the TS will throw an error.
const callback = (a: number, b: string, c: boolean) => {
// ...
}
addBusinessRule('asd', ['sdf', 'sdf', 'sdf'], callback)
TS2345: Argument of type '(a: number, b: string, c: boolean) => void' is not assignable to parameter of type '(...a: string[][]) => void'. Types of parameters 'a' and 'a' are incompatible. Type 'string[]' is not assignable to type 'number'.
Upvotes: 1