Non-null assertion in class property
Asked Answered
P

1

7

I am fairly new to typescript and have scoured the web trying to find an explanation for this.

Recently I have been working on a project and have wanting to use sequelize with it. When reading through the typescript section of the documents, I came across this example below:

// These are all the attributes in the User model
interface UserAttributes {
  id: number;
  name: string;
  preferredName: string | null;
}

// Some attributes are optional in `User.build` and `User.create` calls
interface UserCreationAttributes extends Optional<UserAttributes, "id"> {}

class User extends Model<UserAttributes, UserCreationAttributes>
  implements UserAttributes {
  public id!: number; // Note that the `null assertion` `!` is required in strict mode.
  public name!: string;
  public preferredName!: string | null; // for nullable fields

  // timestamps!
  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;

  //other code
}

Inside the class, the preferredName also has the non-null assertion operator but then proceeds to include null in its type.

Does that override the static type checking because it could possibly be null at runtime (i.e. the user doesn't have a preferred name)?

Or is there a better explanation about why they would include the non-null operator on that property? Such as to exclude undefined but include null.

Paunch answered 28/4, 2021 at 14:24 Comment(1)
To me it looks like it's just for consistency or was created using an automated tool or it's to indicate that while it's not initialized in the constructor it is actually given the correct model value when it is created. I doubt it means that preferredName is not nullCaseate
L
8

This is mostly a terminology problem:

  • null and undefined are different, even though some parts of the language treat them similarly. (For example, the non-null assertion operator eliminates both null and undefined from the domain of the expression it operates on.)

  • The ! after a class property declaration is the definite assignment assertion operator operator, not the non-null assertion operator. (They are both written with a postfix !, but a non-null assertion appears after an expression, while a definite assignment assertion appears after a variable/property declaration.) A definite assignment assertion tells the compiler that it does not need to verify that a variable or property has been initialized before use. The definite assignment assertion operator has nothing to do with null.

If you do not initialize a property or variable, its value will be undefined, not null, if you read from it. If you have the --strictPropertyInitialization compiler option enabled (or just --strict, which includes it), and you have a class property whose type does not include undefined (not null), then you must either initialize it immediately on declaration, initialize it unconditionally inside the constructor, or use a definite assignment assertion:

class Example {
    a: string | undefined; // okay: includes undefined
    b: string | null = "b"; // okay: initialized
    c: string | null; // okay: assigned in constructor
    d: string | null; // error: compiler cannot be sure it is assigned
    e!: string | null; // okay: asserted as assigned

    constructor() {
        this.c = "c";
        if (Math.random() < 1000) {
            this.d = "d"
            this.e = "e";
        }
    }
}

Playground link to code

Laurinelaurita answered 28/4, 2021 at 15:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.