Home > Software design >  Typescript variables with ? and?
Typescript variables with ? and?

Time:10-10

When I declare a variable in typescript like below:

id: number

I get the following error:

Property 'id' has no initializer and is not definitely assigned in the constructor.ts(2564)

If I change the variable to one of the following, the error goes away:

  • id?: number
  • id!: number

What are the differences between those and what situations I should use the above?

CodePudding user response:

When you write this:

class Optional {
  id?: number;
}

you have declared that id is an optional property. That means it may or may not be present in an instance of Optional. If it is present, the value should be a number (or possibly undefined unless you are using the --exactOptionalPropertyTypes compiler option). If it is not present, then if you read the id property, the value will be undefined.

The above resolves the compiler warning because you are not required to initialize a property that's optional; it will just be undefined when you read it.

Anyway that means you cannot just use the id property as a number:

const o = new Optional();
o.id.toFixed(2) // compiler error!
//~~ <-- Object is possibly undefined

That warning is good, right? Because you don't want to accidentally read the toFixed() method of undefined. You need to check for undefined first, possibly by using the optional chaining operator (?.):

o.id?.toFixed(2) // okay

Optional properties are a reasonable approach when you are not sure if the properties will be assigned by the time users want to access them, and you want to protect these users from potential runtime errors.


On the other hand, when you write this:

class Asserted {
  id!: number
}

you have asserted that the id property has definitely been assigned. That assertion is you telling the compiler something it can't verify for itself. This is useful in situations where you actually do assign the property but the compiler cannot follow the logic:

class ActuallyAssigned {
  id!: number
  constructor() {
    Object.assign(this, { id: 123 });
  }
}

An instance of ActuallyAssigned will have the id property set in the constructor, because the Object.assign() function copies properties into the first parameter from all subsequent parameters. But the compiler cannot understand this, and so without the definite assignment assertion, you'd get a warning. Silencing that warning is reasonable here.

But in Asserted above, it's not reasonable. You have lied to the compiler. It can't see that id has be assigned because it actually has not been assigned. And so you will not get a compiler warning if you go ahead and treat the id property like a number, even though it might be undefined:

a.id.toFixed(2) // no compiler error, but
//            
  • Related