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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With