If I have some code like:
type Bravo = {
b?: string;
}
type Charlie = {
c:Bravo['b']
}
function acceptsCharlie(value: Charlie){
//'value.c' is possibly 'undefined'.(18048)
value.c.split('');
}
This makes sense - the type Charlie inherits the optional string from Bravo.
We can solve this by using the Required utility type:
type Bravo = {
b?: string;
}
type Charlie = {
// 👇
c:Required<Bravo>['b']
}
function acceptsCharlie(value: Charlie){
// no error here
value.c.split('');
}
Easy.
However, if I add one more layer of typing look-up-ing:
type Alpha = {
a?: string;
}
type Bravo = {
b: Alpha['a'];
}
type Charlie = {
c: Required<Bravo>['b']
}
function acceptsCharlie(value: Charlie){
//'value.c' is possibly 'undefined'.(18048)
value.c.split('');
}
The Required no longer solves the problem for me.
What gives?
Answer
The difference between ? and | undefined is that in the first situation the field can be skipped, while in the second field needs to exist, even if it can have undefined as a value.
Required works on only optional fields.
If a field is typed as field : X | undefined, then Required will have no effect on it.
Whereas when it is typed as field? : X, then Required works:
What you are doing with the indirection is that now Bravo definitely has the field b, it might be undefined or string, but it is not optional. So in that case Required can not do much.
Excerpt from the release notes of TS 2.8:
Using this ability, lib.d.ts now has a new Required type. This type strips ? modifiers from all properties of T, thus making all properties required.




