typescriptvue.jsgenericsvuejs3

Is there a practical way to infer a list of event names when using defineEmits() in Vue 3?


In a reusable component, I'm attempting to create a simple function that will create proxies for props to bind to children components. They will hold their own internal value and be initialized by the default prop value that could be provided when instantiated in other vue components.

With the given partial example:

const props = defineProps({
  customFilteringToggle: Boolean,
  expanded: {
    type: Array as PropType<string[]>,
    default: []
  },
  modelValue: {
    type: Array as PropType<unknown[] | T[]>,
    default: []
  },
  page: {
    type: Number,
    default: 1
  }
});

const emit = defineEmits<{
  (e: 'update:customFilteringToggle', value: boolean): void,
  (e: 'update:expanded', value: any): void,
  (e: 'update:modelValue', value: unknown): void
  (e: 'update:page', value: number): void
}>();

/**************** Internal Properties ****************/

const createProxy = <T>(prop: () => T, event: string) => {

  const _internal = ref(prop()) as Ref<T>;

  watch(prop, value => _internal.value = value);

  return computed({
    get: () => _internal.value,
    set: value => {

      emit(event, value); // <--- throws error on `event` 'Vue: No overload matches this call.'
      _internal.value = value;

    }
  });

};

const model = createProxy(() => props.modelValue, 'update:modelValue');

const expanded = createProxy(() => props.expanded, 'update:expanded');

const page = createProxy(() => props.page, 'update:page');

const showCustomFiltering = createProxy(() => props.customFilteringToggle, 'update:customFilteringToggle');

Can a list of events be inferred from:

const emit = defineEmits<{
  (e: 'update:customFilteringToggle', value: any): void,
  (e: 'update:expanded', value: any): void,
  (e: 'update:modelValue', value: unknown): void
  (e: 'update:page', value: number): void
}>();

So that when the function is being called, a list of events can be suggested and/or no longer throws an error when providing the value as text? I do similar things with keyof, often. But I'd imagine this would be a bit more in-depth; if it's even feasible.

Ideally, I'd like to avoid casting if possible.


Solution

  • The parameters can be extracted from call signatures with the approach as suggested in this answer, then a tuple can be converted to an intersection:

    type Overloads<T> =
      T extends {
        (...args: infer A1): infer R1;
        (...args: infer A2): infer R2;
        (...args: infer A3): infer R3;
        (...args: infer A4): infer R4
      } ? [
        (...args: A1) => R1,
        (...args: A2) => R2,
        (...args: A3) => R3,
        (...args: A4) => R4
      ] : T extends {
        (...args: infer A1): infer R1;
        (...args: infer A2): infer R2;
        (...args: infer A3): infer R3
      } ? [
        (...args: A1) => R1,
        (...args: A2) => R2,
        (...args: A3) => R3
      ] : T extends {
        (...args: infer A1): infer R1;
        (...args: infer A2): infer R2
      } ? [
        (...args: A1) => R1,
        (...args: A2) => R2
      ] : T extends {
        (...args: infer A1): infer R1
      } ? [
        (...args: A1) => R1
      ] : any
    
    type OverloadedParameters<T> =
      Overloads<T> extends infer O ?
      { [K in keyof O]: Parameters<Extract<O[K], (...args: any) => any>>[0] } : never
    
    type TupleToIntersection<T extends any[]> = Extract<T[number], any>; 
    
    type EmitEvents = TupleToIntersection<OverloadedParameters<typeof emit>>;
    

    Notice that it processes limited amount of values (4) due to the way it works and needs to be extended if necessary.