Reputation: 18427
I would like to start using strictNullChecks
flag in a very big code base. It is definitely a very useful flag but I'm starting to include null types in most of the interface definitions. Maybe it's a personal perception since I never used the flag before, but it's starting to decrease the code readability (imho). I have the sensation that null is everywhere. All originates in a function returning <type> | null
that propagates up in the functions call stack, forcing me to also include null types in interfaces.
Most of the time I have optional nullable fields like:
interface X {
field?: string | null
}
This kind of things are a bit weird when both null and undefined are falsy values.
I've thought about replacing null types with undefined, eg: <type> | undefined
to remove null types from optional interface fields so I just have:
interface X {
field?: string
}
But then I lose the semantic value of null which is something that I would like to maintain.
So, what do you think about strictNullChecks? Do you find this kind of fields field?: string | null
a bit weird or is something that you like and feel comfortable with it?
Upvotes: 2
Views: 1156
Reputation: 111258
I would agree with the answer by Tim Perry with regards to using only one - either null or undefined - and my advice is also to stick with undefined, mostly because you cannot avoid it as this is the value of missing fields and uninitialized variables.
Also null doesn't work for default function parameters:
function f(x = 1) {
// ...
}
Here f(undefined)
will set x
to 1
but f(null)
will not!
The only argument for null is that JSON doesn't support explicit undefined but this is not such a problem as for those cases when you deserialize JSON that needs explicit null values (as opposed to just missing values, effectively meaning undefined) you can easily convert that.
In my projects I go as far as to forbid nulls with a linter config, e.g. for TSLint:
"no-any": true,
"no-null-keyword": true
This witch strictNullChecks
works great in avoiding many kinds of errors.
Remember that the creator of NULL has called it his billion-dollar mistake. Having two nullish values is even worse. But if you have just one (like undefined) and you are using strict null checks in TypeScript then you can be pretty safe from the problems usually caused by null/undefined in most of the languages.
No matter if you go for undefined, null or both, I strongly advice you to always use strictNullChecks because without it your code is not really type safe.
See the TypeScript 2.0 release notes about null- and undefined-aware types:
The type checker previously considered null and undefined assignable to anything. Effectively, null and undefined were valid values of every type and it wasn’t possible to specifically exclude them (and therefore not possible to detect erroneous use of them). [emphasis added]
It is not enabled by default for backwards compatibility with old code that was written before the new way of checking null and undefined was added.
Consider this simple function:
const f = (x: string) => x.toLowerCase();
It is not type safe without the strict flags. It can crash with 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 more info, see:
Update: Soon you will be able to use optional chaining in the example above:
const f = (x: string | null) => x?.toLowerCase(); // OK and safe
and even like this, if x
could be not null but possibly a type without .toLowerCase()
method:
const f = (x: string | number | null) => x?.toLowerCase?.(); // safe as well
For more info, see the documentation about the optional chaining operator.
Upvotes: 0
Reputation: 13216
Personally, I would suggest dropping null
entirely, and using undefined
instead.
If you have a semantic difference between a null
and undefined
value in your application, it's likely to cause confusion (in both TS & plain JS). If you don't, and you're just returning null
/undefined
at random, it'll be simpler and more consistent to use only one.
In terms of which to use, I suggest undefined
primarily because TypeScript has slightly better support for it (i.e. ?
). There are arguments for using only null
instead, it's just more verbose, and it's often convenient to have undefined
as your indicator of missing data because it's the default value of every uninitialized variable/field, and those should usually have the same semantics & behaviour as explicitly unavailable data.
Upvotes: 3