Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript default parameters for objects passed into function

Tags:

typescript

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 :)

like image 685
Tomas Avatar asked Mar 30 '18 06:03

Tomas


Video Answer


3 Answers

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)
    }
    
like image 98
Gerrit0 Avatar answered Oct 29 '22 01:10

Gerrit0


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
})
like image 22
Titian Cernicova-Dragomir Avatar answered Oct 29 '22 00:10

Titian Cernicova-Dragomir


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
like image 1
Creative Learner Avatar answered Oct 28 '22 23:10

Creative Learner