This feels right, and I also have never done it (or had the guts to get others to do it).
The reason I've not is - say there's an optional field. Currently we call that null, probably, and check each time if it's there or not. I could instead make a type, like User and UserWithPhoneNumber. Should we be making types for each combination of present/absent fields? That can't be right.
The classic answer is to move the logic inside the domain object, or have a helper function outside the object, so you aren't constantly checking for field presence/absence, but are instead writing the logic once and calling some code.
I'm not sure in practice types can help with this. But I'd love to be proven wrong.
This explosion of optionality types is (the most important) topic of Rich Hickey's "Maybe Not" talk. I recommend it!
The short version is: the shape of a type is inherent to the type itself, but the optionality of its members is dependent on the situation. A type system that solves this problem separates these concepts to allow for this distinction.
I _suspect_ it's possible to implement something like that in typescript but I haven't tried it myself (and I doubt it's very ergonomic).
if a user with/without phone number are equally valid states to be then types won't help you much. I think it's more about writing
class User{phone: ?PhoneNumber}
over class User{phone: ?string}.
I think this is a slightly different problem. The absence of an optional field, if that's a legal state, is meaningful every time you use the type, so you encode it on the field: `phone: ValidPhoneNumber | null`. When it's not null you're still guaranteed a valid phone number. When it is null, that's a legal state you have to handle and which is domain logic, not validation you forgot to do.
The combinatorial explosion you're picturing only shows up if you make a separate type per combination of present fields, but you don't need to. An independent optional field stays one `T | null`. You only reach for distinct types when fields are correlated and present together because they represent a state, and then it's a discriminated union on a status field, which is N states, not 2^N.