My perspective aligns with Tim Perry's answer regarding the use of either null or undefined, and I recommend sticking with undefined. This is mainly because it is inevitable due to its significance as the value for missing fields and uninitialized variables.
In addition, null poses limitations for default function parameters:
function f(x = 1) {
// ...
}
In this scenario, f(undefined)
will assign x
a value of 1
, while f(null)
will not achieve the same result!
The argument in favor of null rests on JSON not supporting explicit undefined. However, this can be easily addressed when deserializing JSON data that necessitates specific null values instead of simply missing values (effectively denoting undefined).
In my projects, I take the approach of disallowing nulls through a linter configuration, such as in TSLint:
"no-any": true,
"no-null-keyword": true
Utilizing strictNullChecks
effectively helps in preventing many types of errors.
It's worth noting that the individual responsible for creating NULL has admitted to it being his billion-dollar mistake. Having two null-like values could exacerbate the issue. However, opting for just one (like undefined) and implementing strict null checks in TypeScript can significantly mitigate the problems typically associated with null/undefined in most programming languages.
Regardless of whether you choose undefined, null, or both, I strongly recommend utilizing strictNullChecks, as it enhances the type safety of your code.
Reference the TypeScript 2.0 release notes to learn more about null- and undefined-aware types:
Prior to this update, null and undefined were considered assignable to any type, meaning they were valid values for every type, making it challenging to exclude them specifically (thereby preventing the detection of erroneous use). [emphasis added]
By default, this feature is not enabled to maintain compatibility with legacy code written before the updated null and undefined checking mechanism.
Consider this straightforward function:
const f = (x: string) => x.toLowerCase();
Without the strict flags, it is not type safe and is prone to crashing, leading to runtime TypeError exceptions like:
TypeError: Cannot read property 'toLowerCase' of undefined
With the strict flags:
const f = (x: string | null) => x.toLowerCase(); // COMPILE TIME ERROR
const f = (x: string | null) => x && x.toLowerCase(); // OK and safe
For further details, refer to:
Update: Soon, you will have the option to use optional chaining in the aforementioned example:
const f = (x: string | null) => x?.toLowerCase(); // OK and safe
Moreover, if x
may not be null but could be a type devoid of the .toLowerCase()
method:
const f = (x: string | number | null) => x?.toLowerCase?.(); // safe as well
To delve deeper into this topic, explore the documentation on the optional chaining operator.