Subscribe on changes!

defineModel ignores TS types

avatar
Nov 11th 2023

Vue version

3.3.8

Link to minimal reproduction

https://play.vuejs.org/#eNqFUstOwzAQ/JXFlxapNEK9VWklQJUAiYcAwcWXNN2mKY5t+VGKQv6dtUNDQTxO9s7semc9W7MTrYcbj2zMUpubUjuw6LwGkcliwpmznE25LCutjIMaDC6hgaVRFfSorNdRZ6rSLc7ZMAlReJYzLrnMlbQOKlvAJDzQ752jEAqelBGLg94hl2nS9qZOFDistMgcUgSQro6ndR2LmyZNKIpobLc5qtQCBckkmjNIiEuTrpwNSD71XpbFcG2VpBnrUMxZTtWlQHOjXUnaOBtDZAKXkbSXy4g543Gww/MV5s8/4Gu7DRhntwYtmg3N3HEuMwW6lp7dX+OW7h1J2r2g7D/IO7RK+KCxTTv1ckGy9/Ki2otoQSmLBzvbOpR2N1QQGjKbmM8ZORI+7rfRP+WOhqNYx2VDv7hz858l+bA5WPKYCY/k9gKXpcSrAKXWGZIIbyB9NUcTL0LQMVdKYCan/a+b8G0PSqm9A/eqMXSMv7W/AF3XKGV/C5p3e9v5Lg==

Steps to reproduce

Create a Component with a defineModel that got multiple types

<script setup lang="ts">
const modelValue = defineModel<string | number | null | boolean>()
</script>
<template>
  <input type="text" v-model="modelValue">
</template>

Use the component.

<script setup lang="ts">
import { ref } from 'vue'
import Comp from "./Comp.vue"

const msg = ref('Hello World!')
</script>

<template>
  <h1>{{ msg }}</h1>
  <Comp v-model="msg" />
</template>

Observe warning:

image

What is expected?

Expected is that the component accepts the string without any errors.

What is actually happening?

The component throws a warning that it got passed a String instead a Boolean

System Info

No response

Any additional comments?

Im now defineModel is an experimental feature , but i guess the user would expect that it works as the usual prop type definition that throws no warning.

avatar
Nov 11th 2023

I think this is related to special handling for boolean props that we also have in other scenarios.

When we define props with a type interface, we generate the prop runtime definition without a runtime type (because the component will provide TS types to devs):

const modelValue = defineModel<string | number | null>()

// results in runtime prop def:
  props: {
    "modelValue": {},
  },

But when the prop is of type Boolean, we need to know that during runtime for casting props values properly.

const modelValue = defineModel<boolean>()

// results in runtime prop def:
  props: {
    "modelValue": { type: Boolean },
  },

Problem is that we get the same result for a union type containing boolean like in the repro:

const modelValue = defineModel<string | number | null | boolean>()

// results in runtime prop def:
  props: {
    "modelValue": { type: Boolean },
  },

It would need to be:

const modelValue = defineModel<string | number | null | boolean>()

// results in runtime prop def:
  props: {
    "modelValue": { type: [Boolean, String, Number] },
  },
avatar
Nov 12th 2023

I briefly reviewed the source code and it looks like it was intentionally done. In the product environment, supports Boolean and Function when options are passed in. https://github.com/vuejs/core/blob/fc7902982a4bc9f0c9cdb0c9c222bc43a9d5c458/packages/compiler-sfc/src/script/defineModel.ts#L123-L128

avatar
Nov 14th 2023

@RicardoErii This logic is incorrect. No filtering is required if there is a Boolean or Function. https://github.com/vuejs/core/blob/fc7902982a4bc9f0c9cdb0c9c222bc43a9d5c458/packages/compiler-sfc/src/script/defineProps.ts#L262-L276