Tomas
Tomas

Reputation: 3436

Typescript default parameters for objects passed into function

I'd like to initialize my object passed into function in similar manner as it appears to scalar values, e.g.:

someFunction(myValue: number = 5) {  }

So, i keep both type and default value. I'd like to apply similar construct to following:

someFunction(myObj: {val1: number, val2: number}) { }

If I'll go straight ahead and just do the no-brainier copy paste solution:

someFunction(myObj: {val1: number = 5, val2: number = 10}) {}  

Each initialization approach gives a TypeScript erorr A type literal property cannot have an initializer.. Ok, got it, I'm unable to mix type literals with initialization. Using analogy we have:

someFunction(myValue: number = 5) {  }
             ^^^^^^^  ^^^^^^   ^^
              |         |       +-- Initialization value
              |         +-- Type literal
              +-- Parameter name

And analogically:

someFunction(myObj: {val1: number, val2: number} = {val1: 5, val2: 10}) {}
             ^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^
              |         |                              +-- Initialization
              |         +-- Type literal
              +-- Parameter name

Solution is to use way more verbose syntax:

someFunction(myObj: {val1: number, val2: number} = {val1: 5, val2: 10}) {}

The question is it possible, to have an cake and eat it: have concise syntax like in scalar value default value definition (number = 5) and full type control with object definition?

PS I request bonus points for ASCII art :)

Upvotes: 13

Views: 15573

Answers (5)

paoloose
paoloose

Reputation: 171

You have already given the answer. TypeScript, can't do more than that since it doesn't work at runtime, and there is where your default values will be generated and assigned: at runtime. Default parameter values are an ECMAScript feature, not a TypeScript feature.

If you are worried about code polution, you can always do something like this

type FunctionOptions = {
    val1: number,
    val2: number
}

// const assertion to create readonly values
const defaultOptions = {
    val1: 5,
    val2: 10
} as const;

function someFunction(myObj: FunctionOptions = defaultOptions) {
    // ...
}

Of course, for the reasons mentioned, this default values won't get documented in your code. You can use the JSDoc @default to provide this kind of hints.

type FunctionOptions = {
    /** @default 5 */
    val1: number,
    /** @default 10 */
    val2: number
}

Upvotes: 1

Rohìt Jíndal
Rohìt Jíndal

Reputation: 27202

Looks like object literal as a param with default value is behaving different as compare to scalar function param.

A type literal property cannot have an initializer

This error occurs when try to provide a value when typing an object. Hence, to get rid of this we can separate the typing from default values.

function someFunction({val1 = 5, val2 = 8} : { val1?: number; val2?: number }) {
    console.log(val1);
    console.log(val2);
}

someFunction({}) // 👉️ 5, 8

Upvotes: 3

Gerrit0
Gerrit0

Reputation: 9182

If you take advantage of desctructuring, you can reduce a lot of the duplicate code, however it is important to be aware of the differences. Here's the pattern I generally use.

function fun({ val1 = 1, val2 = 2 } = {}) {
    return val1 + val2
}

The signature of this function is correctly inferred as function fun({ val1, val2 }?: { val1?: number; val2?: number; }): number.

However it is important to note that this function differs in a couple important ways from the original.

  1. fun({ val1: 2 }) will result in val2 still being the default value, 2

  2. If you need a required parameter (in that if you provide anything, you must provide it), you have to go back to specifying everything, which quickly becomes even more messy since you have initializers in two locations. This can be mitigated by using an interface to describe the type.

    interface Args {
        val1?: number
        val2?: number
        val3: string
    }
    
    function fun({ val1 = 1, val2 = 2, val3 }: Args = { val3: ' ' }) {
        return val3.repeat(val1 + val2)
    }
    

Upvotes: 10

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249696

There is no built-in way to do this. The simplest solution would be to let the compiler infer the parameter type based on the default value:

function someFunction(myObj = { val1: 5, val2: 10 }) { }

The problem with this solution is that all properties are required on the parameter, so you would have to specify them even if their value would be undefined.

To get around this we can define a helper function that returns a type with all items marked as optional:

function params<T>(o: T): Partial<T> {
    return o;
}
function someFunction2(myObj = params({ val1: 5, val2: 10, otherValueWithoutDefault: null as number })) { } 

Or a version that supports both optional and required parameters:

function params<TRequired>() : <TOptional>(o: TOptional) => Partial<TOptional> & TRequired
function params<TOptional>(o: TOptional) : Partial<TOptional> 
function params<T>(o?: T): Partial<T> | (<TOptional>(o: TOptional) => Partial<TOptional> & T) {
    if(o != null) {
        return o;
    } else {
        return function(o : any) { return o; } as any
    }
}
function someFunction2(myObj = params<{ required: boolean}>()({ val1: 5, val2: 10})) { }

someFunction2({
    required: true,
    val1: 0
})

Upvotes: 3

Alexander Bryksin
Alexander Bryksin

Reputation: 86

You can use interface for it:

interface myObj {
  name: string;
  age: number;
  anyValue?: any;
}

const someFunction = (myObj: myObj) => {
  // method body
};

I hope i right understand your question and help you :) and mb. this: https://www.typescriptlang.org/docs/handbook/interfaces.html , so use interface is good solution.

Upvotes: 0

Related Questions