简体   繁体   中英

Lookup type from generic type can't be assigned to “any”

Alright, this one is a bit hard to explain. My example is convoluted but I tried to simplify it as much as possible while retaining the error.

type Handler = (...args: any[]) => void;

const handlers: { [eventName: string]: Handler } = {};

type Events = {
  // Map event signature (arg types) to event name
  [eventName: string]: any[];
};

function createOnEvent<UserEvents extends Events>() {
  type ValidEventName = Extract<keyof UserEvents, string>;

  return <EventName extends ValidEventName>(
    eventName: EventName,
    eventHandler: (...args: UserEvents[EventName]) => void,
  ) => {
    handlers[eventName] = eventHandler; // [0] ERROR HERE
  };
}

[0] Type 'any[]' is not assignable to type 'UserEvents[EventName]'

Two things confuse me:

  • UserEvents[EventName] should be equal to any[]
  • I'm trying to assign UserEvents[EventName] to any[] , not the other way around...

Btw, this is how this API is used:

type FooEvents = {
  eventOne: [number];
  eventTwo: [string, boolean];
};

const onEvent = createOnEvent<FooEvents>();
onEvent('eventOne', (arg1: number) => null); // OK
onEvent('eventTwo', (arg1: string, arg2: boolean) => null); // OK
onEvent('eventOne', (arg1: string) => null); // error

Which works as expected. The only part that fails is when I want to store the handler in handlers , which should expect functions with any list of args.

I realize this is a bit confusing, so let me know if I can clarify anything. I'd be great to get to the bottom of this. Thanks!

Reason

The error you experience stems from the fact that function types are contravariant in their argument type when the --strictFunctionTypes flag is used.

What does that mean? It means when a function expects some type for its argument, you are allowed to use an argument as specific or wider than that expected type.

In your case, handlers is a dictionary of generic handlers. These handlers expect any[] arguments. Their definition could as well have been written like that:

const handlers: {
  [eventName: string]: (...args: any[]) => void
} = {};

However, your eventHandler is not just a generic handler. It does not accept just any[] arguments, but some very specific ones described by UserEvents . That's against the idea of contravariance — arguments passed to a function must not be more specific than the ones required by its signature. In other words, typeof eventHandler is not assignable to Handler .

What you can do

  • Widen the type of eventHandler locally by using a type assertion:

     handlers[eventName] = eventHandler as Handler; 
  • Make the compiler accept specific definitions of a handler:

     type Handler<T extends any[] = any> = (...args: T) => void; 

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