Use:
interface InputObj {
arg1: number;
arg2: number;
arg3: number;
}
interface ExampleProps {
sum: (input: InputObj) => number
}
Or inline:
interface ExampleProps {
sum: (
input: {
arg1: number;
arg2: number;
arg3: number;
}
) => number;
}
But depending on your use case you may not need to define ExampleProps. Here is your sum function without the arbitrary input object name:
const sum = ({
arg1,
arg2,
arg3
}: {
arg1: number;
arg2: number;
arg3: number;
}) => arg1 + arg2 + arg3;
Answer from automasean on Stack OverflowUse:
interface InputObj {
arg1: number;
arg2: number;
arg3: number;
}
interface ExampleProps {
sum: (input: InputObj) => number
}
Or inline:
interface ExampleProps {
sum: (
input: {
arg1: number;
arg2: number;
arg3: number;
}
) => number;
}
But depending on your use case you may not need to define ExampleProps. Here is your sum function without the arbitrary input object name:
const sum = ({
arg1,
arg2,
arg3
}: {
arg1: number;
arg2: number;
arg3: number;
}) => arg1 + arg2 + arg3;
Here is a fully annotated example as function expression:
const sum: ({
arg1,
arg2,
arg3
}: {
arg1: number;
arg2: number;
arg3: number;
}) => number = ({ arg1, arg2, arg3 }) => arg1 + arg2 + arg3;
Here is another and better alternative for arrow functions. Only arguments are annotated and compiler can infer return type correctly. Function has less clutter and works just as before.
const sum = ({
arg1,
arg2,
arg3
}: {
arg1: number;
arg2: number;
arg3: number;
}) => arg1 + arg2 + arg3;
If you are going to annotate your function in a seperate file:
interface Args {
arg1: number;
arg2: number;
arg3: number;
}
type Sum = (input: Args) => number;
const sum: Sum = ({ arg1, arg2, arg3 }) => arg1 + arg2 + arg3;
You can use any as argument type if argument types are not known. Return type will be inferred as any:
const sum = ({
arg1,
arg2,
arg3
}: any) => arg1 + arg2 + arg3;
So this one is equivalent to previous example:
const sum: ({ arg1, arg2, arg3 }: any) => any
This may not make that much sense for arrow functions but you can set types for known arguments and use key-value pairs for annotating addititional argumens:
const sum = ({
arg1,
arg2,
arg3
}: {
arg1: number;
arg2: number;
arg3: number;
[key: string]: number;
}) => arg1 + arg2 + arg3;
You can also use generics:
interface Args {
arg1: number;
arg2: number;
arg3: number;
}
const sum = <T extends Args>({
arg1,
arg2,
arg3
}: T) => arg1 + arg2 + arg3;
Here is same examples, sum as function statement.
function sum({
arg1,
arg2,
arg3
}: {
arg1: number;
arg2: number;
arg3: number;
}): number {
return arg1 + arg2 + arg3;
}
If you have complicated implementation detail in your function's body, function statement can be better choice for its ergonomics. Plus generics looks less clumsy on function statements.
What are your rules-of-thumb for having a single object as the function arg, versus having many args?
Multiple Params vs "Object" Function Parameter?
Typescript Function/Object parameters - javascript
Typeof arguments object in TypeScript
I'm realising that writing a function with many arguments can lead to difficulties. For example, if you have function foo (a: string, b:string, c:string, ... z: string ) {..} you have an awful lot of code to precisely update when you want to remove the argument c from all calls of foo.
What are the pros and cons of passing many args, versus a single arg object with an associated interface, versus a hybrid approach of primitive args and object args?
Is there an article discussing this?
Reading https://mkosir.github.io/typescript-style-guide/#functions mentions that we should basically always use "options" or some sort of object parameter (Unless it's just "one" param)
However this is the only place I see it, I feel like I actually see this LESS often. Is there any reason of one vs the other when declaring functions?
Thanks
With TypeScript Conditionals (TS v2.8), we can use Exclude to exclude Functions from the object type using Exclude<T, Function>:
let v = {
find: <T extends object>(collection: string, query: object, sortQuery: Exclude<T, Function>, cb?: (a: string, b: string) => void) => {
}
}
// Invalid
v.find('vendors', { type: 'repair' }, (a, b) => { })
v.find('vendors', { type: 'repair' }, 'I am a string', (a, b) => { })
// Valid
v.find('vendors', { type: 'repair' }, { dir: -1 })
v.find('vendors', { type: 'repair' }, { dir: -1 }, (a, b) => { })
A default parameter value can then be set like this:
sortQuery: Exclude<T, Function> = <any>{}
As you can see in the image below, errors are thrown for the first two calls to find, but not the second two calls to find:

The errors that then display are as follows:
- [ts] Argument of type '(a, b) => void' is not assignable to parameter of type 'never'. [2345]
- [ts] Argument of type '"I am a string"' is not assignable to parameter of type 'object'. [2345]
Objects and functions are fundamentally the same thing, but typings help us disambiguate between their functionality.
Consider this:
const foo1: { (): string } = () => "";
The variable foo has a type of object, but that object is callable ... it's a function, and it can indeed be called, but you can't go setting a property called bar on it.
foo1(); // This works
foo1.bar = 5; // This, not so much.
Also consider this:
const foo2: { bar?: number; } = {};
The variable foo has a property called bar on it. That property can be set, but the object can't be called, as it's not typed as callable.
foo2.bar = 5; // This works
foo2(); // This, not so much.
So, lets have a look at your original typing:
sortQuery = {}
sortQuery is an object, but that's all that we know about it. It doesn't have any properties, it's not callable, it's just an object.
We've already seen that a function is an object, so you can assign a function to it just fine. But, you won't be able to call it, as it's not defined as callable.
const sortQuery: {} = () => ""; // This works.
sortQuery(); // This, not so much.
sortQuery.bar = 5; // Nor this.
If you have full control of the source code, then one way to solve this is to move from multiple parameters, to a single parameter with named properties:
const foo = (params: { collection: string, query: object, sortQuery: {}, cb?: Function }) => { };
foo({ collection: "", query: {}, sortQuery: {} }); // Fine
foo({ collection: "", query: {}, sortQuery: {}, cb: () => { } }); // Fine
foo({ collection: "", query: {}, cb: () => { } }); // Not Fine
This removes any ambiguity by requiring names, rather than relying on position in the function call.
My Webstorm IDE suggests that this might be IArguments, which is provided by: lib/es6/d.ts, which is somewhere out there. Maybe someone can verify this is correct, but I am fairly certain.
So the answer would be:
function parse(args: IArguments){
}
and the full signature would be:
function parse(args: IArguments) : Array<any> {
}
since the parse method returns a generic array
You can pick exact type of arguments with tsargs package from npm
Eg:
import { ArgsN } from 'tsargs';
function foo(a: boolean, b: number, c: string) {}
const argsABC: ArgsN<typeof foo> = [ true, 123, 'Hello' ];
In your case:
import { ArgsN } from 'tsargs';
function parse<T extends (...args: any[]) => any>(args: IArguments): ArgsN<T> {
// return parsed arguments
}
// ...
const args = parse<typeof foo>(arguments);
// args -> [ boolean, number, string ];
As already noted in the comments, arrow functions don't have the reserved arguments variable.
But you can still have type safety, a single named argument and default values if you just split up these requirements into different statements. That will also make the code much easier to read and understand:
interface Options {
query: string | null;
}
const defaultOptions: Options = {
query: null,
}
const doThing = async (options: Partial<Options>) => {
const completeOptions: Options = Object.assign({}, defaultOptions, options);
};
Additionally the Options interface actually specifies the strucure that you need inside the function body this way.
Also you can now pass a partial set of options without having to specify all properties literally twice inline, where the value level of the signature -- { query = null } -- specified the default values before, and the type level -- { query?: string; } -- specified both the types and the fact that option keys are optional. The optional part is now done with the Partial type instead.
I think what you are looking for can be achieved by the using overloads of the function. This will allow:
- To have a function with a single parameter.
- Inform the different types to the type system.
- Change the function behavior depending on the type you received. (This violate the OOP principle of Single responsibility, but let's go back to your question...)
So your function overloads would look something like this:
function doThingFunc(arg: { query: string, somthingElse?: string }): void;
function doThingFunc(arg: string): void
function doThingFunc(arg: any): void {
if (typeof arg === 'object') {
console.log(arg.query);
}
if (typeof arg === 'string') {
console.log(arg);
}
}
const doThing = doThingFunc;
doThing('some query');
doThing({ query: 'some query', somthingElse: 'something else' });
Of course, with this implementation you lose the this binding when used inside an object that arrow functions provide out of the box. More details here: https://www.typescriptlang.org/docs/handbook/functions.html