Skip to content

Problem typing optional props with exactOptionalPropertyTypes enabled in tsconfig #6532

Open
@2-5

Description

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

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions