简体   繁体   中英

Type inference without generics?

Suppose I have the following objects:

const arrayOfDifferentComponents: HowDoITypeThis = [

   {
       component: ComponentOne, // no error, keys and value types match
       inputs: {
           key1: "foo"
           key2: 1
       }
   },
   {
       component: ComponentTwo, // error, key2 should be boolean
       inputs: {
           key1: ["foo"]
           key2: 1
       } 
   }
]

class ComponentOne {
   key1!: string;
   key2!: number;
}

class ComponentTwo {
   key1!: Array<string>;
   key2!: boolean;
}

Is it possible to write the type HowDoITypeThis without generics, such that the inputs in the first array item only allow keys of ComponentOne and the inputs in the second item only allow keys of ComponentTwo ?

Just to clarify, I want this type to work with a dynamic number of components and component types.

If the Available Types are Known

You can used mapped types to create a union of possible pairings, but this has some limitations. It will work with a dynamic number of component/type pairs, but not an unknown number.

When you are creating a union through mapped types, basically what you do is create a key-value object type and then take the union of all the values. So the keys get discarded, but we need some sort of key at some point in order to do the mapping from ComponentOne to {component: ComponentOne; inputs: React.ComponentProps<ComponentOne>} {component: ComponentOne; inputs: React.ComponentProps<ComponentOne>} . I'm struggling with what that key would be in this scenario as I'm not seeing any sort of discriminant.

(side note: I find your naming to be confusing because your ComponentOne is the props type rather than the component type, so I'm using names that are clearer.)

If you define some sort of map like this:

type PropTypes = {
    one: ComponentOneProps;
    two: ComponentTwoProps;
}

Then you can use a mapped type like this:

type ComponentAndProps = {
    [K in keyof PropTypes]: {
        component: React.ComponentType<PropTypes[K]>;
        inputs: PropTypes[K];
    }
}[keyof PropTypes];

Which gives you the union of all valid pairings:

type ComponentAndProps = {
    component: React.ComponentType<ComponentOneProps>;
    inputs: ComponentOneProps;
} | {
    component: React.ComponentType<ComponentTwoProps>;
    inputs: ComponentTwoProps;
}

Your HowDoITypeThis is an array ComponentAndProps[] . You'll get a big red error if you try to assign ComponentOneProps to a ComponentTwo component.

TypeScript Playground Link


If the Available Types are Unknown

You need a different approach if you want your array to accept any type of component, but enforce that the component and input properties match. This does require generics. It also requires that you create arrayOfDifferentComponents through a function because we cannot say its specific type. We need to infer its generic and check that that the provided array is correct for that generic.

You can create a mapped type that maps from a tuple of prop types to a tuple of component / inputs pairs:

type MapToPairing<T> = {
    [K in keyof T]: {
        component: React.ComponentType<T[K]>;
        inputs: T[K];
    }
}

And use an identity function to make sure that your array is valid:

const createComponentArray = <T extends {}[]>(array: MapToPairing<T>) => array;

You do get the expected error when your array includes an element with mismatched component and inputs properties.

TypeScript Playground Link

you can use typescript tuple such as

type HowDoITypeThis = [
   {
       component: ComponentOne;
       inputs: {
           someKeyOfComponentOne: ComponentInputA;
       };
   },
   {
       component: ComponentTwo;
       inputs: {
           someKeyOfComponentTwo: ComponentInputB;
       };
   }
]

of course you can also do this

interface MyGenericA<T, U> {
   component: T;
   inputs: U;
}

type HowDoITypeThis = [
   MyGenericA<ComponentOne, YourInputTypeA>,
   MyGenericA<ComponentTwo, YourInputTypeB>
]

you basically had it:

type HowDoITypeThis = [
   {
       component: ComponentOne,
       inputs: {
           someKeyOfComponentOne: ComponentOne[someKeyOfComponentOne]
       }
   },
   {
       component: ComponentTwo,
       inputs: {
           someKeyOfComponentTwo: ComponentTwo[someKeyOfComponentTwo]
       }  
   }
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM