I am trying to make a props type for a React component that can have certain fields but can also have additionally two more fields. But they have to be both specified, or not specifed at all.
I was trying to achieve this with the following example:
interface Base {
f: string;
d: string;
}
interface ExtendedBase extends Base {
a: string;
b: string;
}
const obj: Base | ExtendedBase = { // shouldn't this give an error that 'b' is missing?
f: '',
d: '',
a: ''
}
I thought that with union it will require 2 Base fields or 4 ExtendedBase fields, but in the example above specifiying only 3 fields seems to satisfy the union, but why?
Any thoughts? Much appreciated.
CodePudding user response:
As an alternative to your type definitions, I would suggest making use of Discriminated unions. This way, you could refactor your code as:
interface Common {
f: string;
d: string;
}
interface Base extends Common {
type: 'base'
}
interface ExtendedBase extends Common {
type: 'extendedBase'
a: string;
b: string;
}
const obj: ExtendedBase | Base = { // gives an error that 'b' is missing
type: 'extendedBase',
f: '',
d: '',
a: '',
}
CodePudding user response:
I think, you can make use of Type Aliases
For Example,
type MyType = {
a?: true,
b?: string
} | {
a?: false,
b?: string
}
Here, if a
is assigned/true
then b will be required, and if you wish not to assign or keep a
as false
, then b
won't be required.
Hope this helps somehow!
Good Luck :-D
CodePudding user response:
The obj
variable as you defined it can contain a Base
or an ExtendedBase
but the constraint when assigning it is actually "each property that is in one type and the other must exist in the assigned value".
The actual keys that must be in a value being assigned to obj
are only f
and d
, and you can assign { f: '', d: '', a: '' }
because it has at least f
and d
.
If you want to keep this data structure, you could keep this solution and take advantage of the type narrowing which is based on the control-flow:
interface Base {
f: string;
d: string;
}
interface ExtendedBase extends Base {
a: string;
b: string;
}
const obj: Base | ExtendedBase = {
f: '',
d: '',
a: '',
}
if(!('a' in obj)) {
// obj is considered to be of type Base in this code block
obj.a; // Property 'a' does not exist on type 'Base'.
obj.b; // Property 'b' does not exist on type 'Base'.
obj.d; // OK
obj.f; // OK
} else {
// obj is considered to be of type ExtendedBase in this code block
obj.a; // OK
obj.b; // OK
obj.d; // OK
obj.f; // OK
}