0

I want to be able to extend a base interface at one or more specic locations. The idea is to be able to define a base state for the library xstate which can be extended for more specific purposes.

I have

interface Base {
   id:string;
   states:{
       init:{},
       loading:{},
       idle:{
            states:{
                  someBaseState:{}
            }

       }
   }
}

I want to know if it is possible with typescript to extend the base interface at a specific location. e.g the "idle" property

interface Page "extends Base Idle Property" {
   id:string;
   states:{
       someOtherState:{}
   }
}

so that the result is

{
   id:string;
   states:{
       init:{},
       loading:{},
       idle:{
            id:string;
            states:{
                someBaseState:{}
                someOtherState:{}
            }
       }
   }
}

I know that I can define Generic in Typescript like this

interface Base<T> {
  id: string;
  states: {
    idle: T
  }
}

But I want to be able to define specific base properties for the state "idle" (for example) and not completely implement it each time.

Han Che
  • 8,239
  • 19
  • 70
  • 116
  • I don't see how you'd expect to combine `Base` and `Page` to yield your desired output, since `someOtherState` appears to be nested four levels down in your desired output but only two levels down in `Page`. Could you please make sure that all your code constitutes a [mcve] so that someone could suggest a reasonable answer? My guess in cases like this would be to use something like generics or an intersection instead of inheritance, but without understanding your question I wouldn't want to begin to develop an answer. Good luck! – jcalz Jun 30 '19 at 18:22

1 Answers1

0

Given these definitions:

interface Base {
  id: string;
  states: {
    init: {};
    loading: {};
    idle: {
      states: {
        someBaseState: {};
      };
    };
  };
}

interface Page {
  id: string;
  states: {
    someOtherState: {};
  };
}

the easiest thing to do might be to use an intersection instead of inheritance, like this:

type MyNewType = Base & { states: { idle: Page } };
interface MyNewInterface extends MyNewType {} // if you want in interface

You can see it conforms to the shape you want:

function foo(mni: MyNewInterface) {
    mni.states.init; // okay
    mni.states.idle.id; // okay
    mni.states.idle.states.someBaseState; // okay
    mni.states.idle.states.someOtherState; // okay
}

That type might be a bit difficult to understand as an intesection... if you really want to you can use a nested mapped type like this:

type NestedId<T> = T extends object ? { [K in keyof T]: NestedId<T[K]> } : T;
type NestedExtend<T, U> = NestedId<T & U>;

type MyNewType2 = NestedExtend<Base, { states: { idle: Page } }>;

which shows you the following type when you inspect via IntelliSense:

// IntelliSense shows you
type MyNewType2 = {
    id: string;
    states: {
        init: {};
        loading: {};
        idle: {
            states: {
                someBaseState: {};
                someOtherState: {};
            };
            id: string;
        };
    };
}

Either way should work. Hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360