Earlier quoted context omitted.
As someone who really appreciates strong typing, I will die on this hill - I hate Typescript. Introducing a transpiled typing system on top of a dynamically typed language is a recipe for all sorts of insane complexity, which is what I experience whenever I try to use TS. If you consider all the time and effort it took to introduce types for every NPM project out there, all the development of the language itself, the…
Did a personal web project and chose TypeScript to see what the rage was about. I spent a ridiculous amount of time trying to design types. At some points I couldn't figure it out, or I ran into some limitation of the language to express what I need. So my code has a bunch of exclamation points everywhere to assert that a null ain't coming. It feels dirty. Just too hard to use for me.
Are you using "?" optional fields in your classes/interfaces a lot? If so, do you need to? Unless it's truly optional, you're basically making the type system less useful if you put that all over the place. If you have a lot of sparse objects, it's probably an indicator of a data type doing too much. You might be much better off splitting it into smaller interfaces and using union types when you need combined objects.
Even if you can't do the above, checking for null at the start of a function/block will "prove" to the compiler that it's not-null for the rest of the block, so an upfront check can save you from a lot of "!" assertions.
Even if you can't do either of the above, you can probably use the elvis operator "?." to safely dereference things that might possibly be undefined.
In my experience with typescript, there's a few good rules of thumb and things to keep in mind:
- Prefer "interface" over "class" for describing most data types. Especially plain old data objects that don't have methods. I'd forget about the definition of "interface" you might know from Java. It can be used that way, but it can also be used like you would use "struct" in C
- Use the "Partial" modifier instead of using "?" in the base structure if you need to do something like sparse updates. For instance a pattern I use a lot is:
interface MyStruct { a: number, b: string }
function update(original: MyStruct, updatedFields: Partial) {
return {...original, ...updatedFields}
}
- Always prefer readonly fields and non-optionality.- If you're not worried about GC pressure, it's almost always better to return a new object than mutate an old one. The spread operator is great for this.
- Within reason, it's better to prefer smaller data structures and use type unions when you need fields from both in one object.