TypeScript: Guidelines for choosing between `null` and `undefined` with `strictNullChecks`
Whenever you want to make a type optional you have to choose what value to use for the missing values:
- undefined: Has the benefit that is already there when you don’t write anything.
- lib.d.ts contains 12 references of
| undefined.
- lib.d.ts contains 12 references of
- null: Familiarity, better interaction with REST Services (undefined disappears).
- lib.d.ts contains 473 times of
| null.*
- lib.d.ts contains 473 times of
- undefined | null: Is too long, I was surprised that the type expression
T?is not introduced as equivalent toT | null | undefined. BTW this example is misleading.
In my opinion for most of the cases the distinction is useless as long as you use non-strict comparison (==. =!) and boolean operators ( && ||).
Unfortunately writing | null | undefined is way too cumbersome, specially if you need have more than one optional thing in a type expression.
This is a real case:
Promise<Array<T | null | undefined> | null | undefined> | null | undefined
Some recomendations?
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 20 (12 by maintainers)
One thing to note is that “optional” is really
T | undefined; this is how the JS values will be initialized at runtime. and optional or undefined parameter, property or variable is initialized toundefined, and not to null.nullis a sentinel value that is used to signal the lake of a value. you have to set that up yourself, and it is not done to you by the engine.Without going into ideological arguments, might be worth watching Douglas Crockford’s video when he talks about this in details; the main issue is most programming language use one value to indicate what
undefinedandnullare used for. so in a sense JS has one too many notion of nothing-ness.The TS compiler internally opted to use only
undefined. Part of that is we do not have dependencies on other libraries, so we can be as prescriptive as we want. Some APIs, e.g. DOM, will returnT | nullconsistently, so there is no way around undefined. So i would say it is up to you and your dependencies.If you want to use both
nullandundefinedi would recommend adding a new type alias:type Maybe<T> = T | null | undefined;and using that everywhere.If indexers contained undefined every array access a[i] would be T | undefined. This is very annoying and would be borderline unusable.
For future readers, this isn’t quite true. You can’t define an interface with a property
T | undefinedif you want that property optional. Instead, you must use the?:syntax to have an optional interface property:interface I { name?: T }Usinginterface { name: T | undefined }will force you to explicitly assignundefinedto propertyname, but it will not allow you to leave it out. Unfortunately, this means that there are really 3 types of “no value” in TypeScript (?:,undefined,null).@aluanhaddad Sorry I wasn’t more clear, that is what I meant . Will edit my comment.
I know, I’ve been very active in this thread and in the previous one in Codeplex, but it has been delayed for C# 8 :S
Well,
!is just a shortcut for a casting to the non-nullable version of the type.As any casting the compiler asks the developer to pay special attention to a potentially unsafe action, and in exchange the compiler trust the developer criteria.
Using an indexer is unsafe, because independently of the declared return type, potentially you’ll get back some
undefinedvalues.Example:
I think it’s obvious that the type of
cshould benumber | undefinedfrom a formal point of view.Also the
undefinedgets introduced in the indexer action, not the data structure itself, becauseObject.values(cache)should be of typenumber[].Something different is that this would be annoying in many other practical examples, but I suppose I have now a thicker skin after my quest to enable
strictNullChecks((https://github.com/signumsoftware/framework/commit/88ad60c81fa004ad095b75e101806f6159ef8999), (https://github.com/signumsoftware/framework/commit/b4804201e69be8cca2e005e537c4c3727b5d0803))Just after turning the flag on, I’ve found 800 errors in a ~50 files project, of witch I’ve solved until now 600 (two evenings).
The investment necessary to introduce the flag is quite high. I was expecting something like 4 compile-time errors for each potential run-time bug fund… but it’s more like 50, and what is worst, the way to solve most of them is by adding
| undefinedand!everywhere, making the code harder to read.Here are some things that are creating me pain :
nullandundefined:. Since you took the safe/strict choice of not deciding whatT?means, now every project has to do it.renderfunction acceptsnullbut notundefinedas a return value. It’s happy aboutundefinedinside theReact.Elementshowever. Quite odd…setState: I’m aware is a well known issue (https://github.com/Microsoft/TypeScript/issues/6613, https://github.com/Microsoft/TypeScript/issues/6218, https://github.com/DefinitelyTyped/DefinitelyTyped/issues/7987) but is even more painful withstrictNullCheck.nullandundefinedare serialized differently,For me this has important consequences, since in one case the property in the server side will be ignored (
undefined) while in others will be set tonull.thenand returningundefinedand aPromise<T>he is not able to infer that the return type isPromise<T | undefined>(https://github.com/signumsoftware/framework/commit/88ad60c81fa004ad095b75e101806f6159ef8999), this is quite annoying for UI modal workflows that can be cancelled at any point.Sorry for my long rant, I’m a very enthusiastic Typescript and C# developer and I had lots of hopes in non-nullable references. I also develop the framework in my free time. If I consider this a tough pill to swallow, I can not imagine in a more enterprise environment…
What’s even worst, I think there is not too much that Typescript or any language that deals with the real world can do to solve the problem once and for all… looks to me that there are lots of variables that are nullable only in 5% of the cases, so the compiler gets a lot on the way to provide little help.
The expression
has type
any[]. Sinceundefinedis a subtype ofany,undefinedis included.In my quest to use
strictNullChecksI’m finding some things that could be interesting. I’m going to write them all here but feel free to ask me to move to a different issue if the thing gets complicated:render()method throws an error ifundefinedis returned, but he is happy withnull. This makes a nasty exception for my “replace allnullbyundefined” strategy… if you updaterender()in react.d.ts it becomes a compile time error, so it’s OK..d.ts, is there any reference declaration type that is updated forstrictNullChecksalready?strictNullChecks, is there any value on making non-nullable indexers, like this:there is always going to be some key for witch you won’t have a value…