Home > database >  Overwrite a type to make it more concise when optional
Overwrite a type to make it more concise when optional

Time:04-29

Given this example:

type A = (arg?: string) => void

const myObject : { a: A } = {
    a: (arg: "yes" | "no") => { console.log(1)}

 } 

I get an error:

Type 'string | undefined' is not assignable to type '"yes" | "no"'.
Type 'undefined' is not assignable to type '"yes" | "no"'

Even if I make arg?: "yes" | "no" it then returns a:

Type 'string | undefined' is not assignable to type '"yes" | "no" | undefined
Type 'string' is not assignable to type '"yes" | "no" | undefined'

I wonder if there is any way that I can make arg more precisely typed ("yes" | "no" instead of string) when is an optional argument. Assume that I cannot touch type A

CodePudding user response:

This is fine:

const a1: 'yes' | 'no' = 'yes'
const a2: string | undefined = a1

This is not:

const b1: string | undefined = 'yes'
const b2: 'yes' | 'no' = b1 // Error: Type 'string' is not assignable to type '"yes" | "no"'

In functions as well:

type A = (arg?: string) => void
const f: A = (arg: 'yes' | 'no') => { console.log(arg.toUpperCase()) }
// Error: Type '(arg: 'yes' | 'no') => void' is not assignable to type 'A'

If we ignore the TypeScript error there is a risk to get a runtime error:

const f: A = ((arg: 'yes' | 'no') => { console.log(arg.toUpperCase()) }) as A
f(undefined) // Runtime Error!

arg?: "yes" | "no" is not the answer because there is still a risk:

const f: A = ((arg?: 'yes' | 'no') => {
  if (arg) {
    console.log(arg[1].toUpperCase()) // log second char as upper case
  }
}) as A
f('A') // Runtime Error!

CodePudding user response:

You have a choice to make. Option 1:

type A = (arg?: 'yes'|'no') => void
const myObject : { a: A } = {
    a: (arg?: 'yes'|'no') => { console.log(1)}
}

Option 2:

type A = (arg?: string) => void
const myObject : { a: A } = {
    a: (arg?: string) => { console.log(1)}
}

CodePudding user response:

The contract (interface) is determined by the type A.

You can't assign something that is narrower (or different), because when you call myObject.a you would be using the contract defined by A. So what you assign MUST accept the shape defined by A.

myObject.a('foo')  // valid, as 'foo' is a string, as per A's interface

To allow assignment of something other than A to myObject.a would mean that the supplied function could receive something unintended, which would defeat the purpose of type safety.

If you can't change the definition of A, then you will have to implement the function assigned to myObject.a with the same interface as A, and make a runtime check to see if the supplied arg conforms to "yes" | "no". A simple comparison will suffice, or if it becomes more complex, you can consider a type guard, which will narrow the scope for the remainder of the block.

  • Related