Subscribe on changes!

`defineComponent` prop types are broken in "Functional Signature" mode

avatar
Nov 21st 2023

Vue version

3.3.8

Link to minimal reproduction

https://play.vuejs.org/#eNrVU1Fv0zAQ/isnv6yT1kSwF9S1ZYCGBA8MQd9mQCG5Zh7O2bKdrqjqf+fs0JJEY9rrXpL4vu9833132Yk31mabFsVMzH3plA3gMbQWdEH1QorgpVhKUo01LsA701hYO9PASZbHQ0w9uRjg79UWqwEpRQ5MSeG3RVjx43r9AhYQj2adUhnOcwD4/nrEeznkpQv75HneaV+KM1ZcGlqrOrvzhritnSQAKUrOUxrdtQ3KEHc1g4RErNDa3H9MseBaPDvEy1ssfz0Qv/PbGJPis0OPboNSHLFQuBpDB199/YRb/j6CjalazexHwC/ojW6jxo72tqWKZfd4Se2HZLiieuWvtgHJH5qKQiNzn/hSsO3Rsv+1/k/ueXae8iTt2cXDcHt78eBG7KDCtaJUwxBSgP3f2R/njdvEZF7R6vTu8yex6Hy1nPxgVdD4egarC9DKB/64+Qb7U1gsYZKe1Gqd2tqBdcb6Gdywbb5mc7iVmCMFZzDldLgTndhpU9jRVnTA0BIW3ll/GwLXyPOyIk6rUKuNywhDTrbJL5mWu5aCanBameaS/cte5RWL6Icz9M30pzP3vCcZi674pt4s4yVpg9zUIVXo4qSfVnuU1q8/ggYaYvnRnI//5zMcduGBkJuNyeO57/8ALAehiQ==

Steps to reproduce

I have basically taken an example from the docs and simplified it a bit:

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent(
  <T>(_: { msg: T; list: T[] }) => () => null,
  { props: ["msg", "list"] },
)
</script>

What is expected?

I expected the type of this component after import to be the following:

import Comp from "./Comp.vue";
type Temp = typeof Comp;
//   ^? type Temp = <T>(props: { msg: T; list: [T] } & {}) => any

What is actually happening?

any is replacing the T parameters:

import Comp from "./Comp.vue";
type Temp = typeof Comp;
//   ^? type Temp = (props: { msg: any; list: any } & {}) => any

System Info

No response

Any additional comments?

A workaround is casting props to never[]:

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent(
  <T>(_: { msg: T; list: T[] }) => () => null,
  { props: ["msg", "list"] as never[] },
)
</script>

Then typeOf Comp returns expected type.

avatar
Jan 10th 2024

Again, how do you set EmitsOptions in Functional Signature mode ?

import type { SetupContext } from 'vue';

const Comp = defineComponent(
  <T extends string | number>(
    props: { modelValue: T },
    ctx: SetupContext<{ 'update:modelValue': (val: T) => void }>
  ) => {
    return () => {
      return <div>{props.modelValue}</div>;
    };
  },
  // manual runtime props declaration is currently still needed.
  {
    props: ['modelValue'] as never,
    emits: ['update:modelValue'],
  }
);

expected

const Comp: <T extends string | number>(props: {
    modelValue: T;
} & {
    "onUpdate:modelValue"?: ((val: T) => void) | undefined;
}) => any

now

const Comp: <T extends string | number>(props: {
    modelValue: T;
} & {
    "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
}) => any
avatar
Jan 13th 2024

@wangcch Here's what I came up with:

<script setup lang="ts">
import { defineComponent, h } from 'vue';

const Comp = defineComponent(
  <T extends string | number>(props: {
    modelValue: T;
    "onUpdate:modelValue"?: (val: T) => void;
  }) =>
    () => h("div", props.modelValue),
  { props: ["modelValue", "onUpdate:modelValue"] as never },
);

// const Comp: <T extends string | number>(props: {
//  modelValue: T;
//  "onUpdate:modelValue"?: (val: T) => void;
// } & {}) => any

// You have to narrow down the type of your component beforehand, because of
// https://github.com/vuejs/language-tools/issues/3745
const CompString = Comp<string>;
</script>

<template>
  <CompString model-value="123" @update:model-value="(x) => x" />
</template>

playground.

avatar
Jan 13th 2024

@rijenkii Thank you very much ~ Cool~ This is indeed a way. v-model also works.

-- { props: ["modelValue", "ononUpdate:modelValue"] as never },
++ { props: ["modelValue", "onUpdate:modelValue"] as never },

input playground