core
core copied to clipboard
Problem typing optional props with exactOptionalPropertyTypes enabled in tsconfig
Vue version
3.2.37
Link to minimal reproduction
https://sfc.vuejs.org/#eNqdUzuP1DAQ/itWqpx0iREnhBSyuaUAiQaoqNxkk9nFt/FDfgShkP/OOE72QnK6gs6eGX+PmfGQfNQ67z0kRVI6ELqrHVRMElJ+kdq7r16cwJA+E6qF7sCSvu48sITQWKSrYSBTjDw+EpZ42cIZ0+NYUh1LTt45Jcmx6XhzXRDIgbx7w5Lq27WksQCLS7pSgFfbGK4dseC8Jl0tL/jaWXzFJBdaGUcGYuBMRnI2SiA7+mDJLbk2MBfkdBUMtkM5k42S1s02DgGylPHZHzIZ4hLaKr0d7z4ErVEdaknuk8iYiVrnT1ZJbOYQvLM5gZoLMkVCbKIt8PDTOW0LSr3U10veKEGPmKPGS8cFZK0Sx4f8bf7wnrbcunU8Byuyk1G/LNp4Qvj7FTjFYA8mM4CCDZhXyTa1/xBucjvSwDkyOWIDNm3dbxMPBas9arxBXPdjXif3WwNGY9vn9fqfdUBfyAPtbieWIWujtMUhx1F+D7dymswkbJJTkP300WWVhrkvOCC4u8F8wssMk+Jz/Aa6RdnFMyZOKO7XS+B3BekV33Ose4Rci7d0IrqAS/HhHm1ZNIN9MjIazp+VID5ObdoX7GS6FWWd4fLygroFdvklujYWPneqnjGC8JAPjUlf60AsDArG7Uca/wK66Je8
Steps to reproduce
See reproduction link for full example.
I declare a property which allows undefined:
const props = defineProps<{
modelValue: number | undefined
}>();
and actually pass undefined:
<template>
<TheComponent v-model="value" />
</template>
<script setup lang="ts">
const value = ref<number | undefined>(undefined);
</script>
What is expected?
Vue doesn't emit runtime errors for explicitly allowed and used undefined properties.
What is actually happening?
I get the following Vue runtime error in dev mode
[Vue warn]: Invalid prop: type check failed for prop "modelValue". Expected Number | Null, got Undefined
This is because the SFC compiler emits the following property declaration. Note how null was used instead of undefined:
props: {
modelValue: { type: [Number, null], required: true }
},
If I modify the generated code and runtime like below (see <<< and >>>), to allow undefined, it seems to work correctly. But I don't understand if there are other reasons for not allowing undefined to be a required property value:
// Component
props: {
modelValue: { type: [Number, undefined], required: true }
},
// Vue Runtime
function getType(ctor) {
const match = ctor && ctor.toString().match(/^\s*function (\w+)/);
// <<<<<<<< added `undefined` support >>>>>>>>>>
return match ? match[1] : ctor === null ? "null" : ctor === undefined ? "undefined" : "";
}
function assertType(value, type) {
let valid;
const expectedType = getType(type);
if (isSimpleType(expectedType)) {
const t = typeof value;
valid = t === expectedType.toLowerCase();
if (!valid && t === "object") {
valid = value instanceof type;
}
} else if (expectedType === "Object") {
valid = isObject(value);
} else if (expectedType === "Array") {
valid = isArray$1(value);
} else if (expectedType === "null") {
valid = value === null;
// <<<<<<<< added `undefined` support >>>>>>>>>>
} else if (expectedType === "undefined") {
valid = value === undefined;
} else {
valid = value instanceof type;
}
return {
valid,
expectedType
};
}
System Info
System:
OS: Windows
Binaries:
Node: 18.7.0
npm: 8.15.0
npmPackages:
vue: ^3.2.37 => 3.2.37
Any additional comments?
I'm using undefined when a value is not set by the user, for example empty number <input> or unselected <select>, as I understood to be current recommended practices:
Use undefined. Do not use null. https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#null-and-undefined