Evgeniy Pozdnyakov
Evgeniy Pozdnyakov

Reputation: 73

Extending type with an optional type

I have a base type

type BaseProps = {
  foo: boolean;
  bar?: string;
};

This base type can be extended with 2 additional types:

type WithOkButton = {
  useOkButton: boolean;
  onOK(): void;
};

type WithCancelButton = {
  useCancelButton: boolean;
  onCancel(): void;
}

My goal is to have a type which has baseProps and all the combinations of additional types:

I can achieve my goal like this, but I don't like it. Can you please help me to find a better way?

type BaseWithOK = BaseProps & WithOkButton;

type BaseWithCancel = BaseProps & WithCancelButton;

type BaseWithBoth = BaseProps & WithOkButton & WithCancelButton;

type ResultType = BaseProps | BaseWithOK | BaseWithCancel | BaseWithBoth;

Update

Here is how the code will look like if I need all the combinations for three buttons

type BaseProps = {
  foo: boolean;
  bar?: string;
};

type A = {
  useA: boolean;
  onClickA(): void;
};

type B = {
  useB: boolean;
  onClickB(): void;
};

type C = {
  useC: boolean;
  onClickC(): void;
};

type BasePropsWithA = A & BaseProps
type BasePropsWithB = B & BaseProps
type BasePropsWithC = C & BaseProps
type BasePropsWithAB = A & B & BaseProps
type BasePropsWithBC = B & C & BaseProps
type BasePropsWithAC = A & C & BaseProps
type BasePropsWithABC = A & B & C & BaseProps

type Props = BaseProps | BasePropsWithA | BasePropsWithB | BasePropsWithC
| BasePropsWithAB | BasePropsWithBC | BasePropsWithAC | BasePropsWithABC;

Upvotes: 2

Views: 158

Answers (3)

Evgeniy Pozdnyakov
Evgeniy Pozdnyakov

Reputation: 73

I guess I found a solution. It can be done with help of an utility type like this:

type Optional<T> = T | {};

It allows to make all the possible combinations of additional types in one line of code:

type Props = BaseProps & Optional<WithOkButton> & Optional<WithCancelButton>;

So I suggest to solve the initial issue like this:

type Optional<T> = T | {};

type BaseProps = {
  foo: boolean;
  bar?: string;
};

type WithOkButton = {
  useOkButton: boolean;
  onOK(): void;
};

type WithCancelButton = {
  useCancelButton: boolean;
  onCancel(): void;
};

type Props = BaseProps & Optional<WithOkButton> & Optional<WithCancelButton>;

What do you think?

Playground

Upvotes: 1

tenshi
tenshi

Reputation: 26324

Essentially, you want combinations of the extensions all intersected with the base, so here I have a type that generates combinations from a tuple adapted from this JS impl:

type CombinationsImpl<Active extends ReadonlyArray<unknown>, Rest extends ReadonlyArray<unknown>, A extends ReadonlyArray<unknown>> =
    [Active["length"] extends 0 ? true : false, Rest["length"] extends 0 ? true : false] extends [true, true]
        ? A
        : Rest["length"] extends 0
            ? [...A, Active]
            : Rest extends [infer Head, ...infer Tail]
                ? [...CombinationsImpl<[...Active, Head], Tail, A>, ...CombinationsImpl<Active, Tail, A>]
                : never

type Combinations<A extends ReadonlyArray<unknown>> = CombinationsImpl<[], A, []>;

Then all we need to do is "loop" over the tuples with distributive conditional types and do the intersecting:

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type CreateProps<Base, Exts extends ReadonlyArray<unknown>> =
    Combinations<Exts>[number] extends infer U extends ReadonlyArray<unknown>
        ? U extends U
            ? Base & UnionToIntersection<U[number]>
            : never
        : never

Keep in mind that for larger inputs, this may slow down your TSServer (thing that provides autocomplete/intellisense).

Playground

Upvotes: 0

Usama Arslan
Usama Arslan

Reputation: 111

According to official typescript docs here it is a recommended method.

Upvotes: 0

Related Questions