Subscribe on changes!

App fails at runtime even though TS and Volar are happy with generic defineComponent Props type

avatar
Mar 5th 2023

Vue version

3.2.47

Link to minimal reproduction

https://sfc.vuejs.org/#eNqNUsuO1DAQ/JXGl9mVJrG4RmEkxAUuCGkRJ1+8SWfiVWIbt7NLFOXfaecxWRYhccijbXdXuaom8dH7/HlAUYiSqmB8hE7b6wclIilxUVZF03sXInxyvYcmuB5OuUxFajspC5Ae/LUcqrHRQxdhSmsAFR9zFm2kYl+CZdB5Leb04VcpV+yEZ8uIve90RK4AyndZBj9cpwNPCwGr2I1ArXsh0BYwBBegxYBgGiDX47fgPBgC6yJ4TYQ1ZNkx6Q6f0cKLia0btjuRx8o0o7FXCIPl6yJ4HkL3t8blWJGmZ2mHxfnsXNCjEvKY/H30+LAqqG29UdZMrNXej6CbiGFhlIB2pmd4ZBo7bKNNRytqKV+pIM5iNSHrtc+fyFm2a9FTbRvs1U1hJdiYVCvRxuipkJKaKrn1RLkLV8l/+QaZI/XZY2A1MfBgJTZDZobcPf5nNAC2aEzJd2MxdSx2w7wl5XVEpISHQ+rYIiQFCCILB7VDsieWAn8OhkXTf3rBABXrqaNxnJC/4vYGvpxuAhdAMSS8+XI3zfcsD9P46lbQIz3/G8HURkfONIdwB0hjzmnly6lO/DiptwC17y8HoelgB/NcSt586/f8GwO4PA8=

Steps to reproduce

App fails at runtime even though TypeScript and Volar are happy with defineComponent Props type.

App.vue

<script lang="ts">
  import Comp from './Comp.vue'
  
  export default {
    components: {
      Comp,
    }
  }
</script>

<template>
  <!-- Volar correctly shows an error here if someProp is not passed -->
  <!-- (even without Comp specifying runtime props) -->
  <Comp :some-prop="Hooray"/>
  <!-- TypeScript and Volar are happy after passing someProp, but runtime fails -->
</template>

Comp.vue

<script lang="ts">
  import { defineComponent } from 'vue'
  
  // Specifying the Props type doesn't require a runtime props declaration
  export default defineComponent<{ someProp: string }>({})   // No type error here
</script>

<template>
  <!-- Volar types someProp as a string here, as I'd expect -->
  <h1>someProp: {{ someProp }}</h1>
</template>

What is expected?

Initially from reading the Vue docs, I expected to see a type error about not specifying the runtime props in the object passed to defineComponent along the lines of "Type undefined not assignable to (whatever my prop types were)".

BUT when I realized that TypeScript was happy AND that Volar was correctly seeing the prop type in the template AND that Volar was seeing the prop as required in the parent component's template...I started to think that this would "just work" even in runtime.

What is actually happening?

Sadly the app still failed at runtime with the error

Property "someProp" was accessed during render but is not defined on instance.
 at <Compsome-prop=undefined>
 at <Repl>

But then I thought...why does that need to fail? Ultimately I don't care about the runtime validation if TypeScript and Volar are already validating the props as I'm writing the code. Now runtime validation of component props feels like trying to do runtime validation of function params - we don't really need it.

System Info

System:
    OS: macOS 12.6.3
    CPU: (10) arm64 Apple M1 Pro
    Memory: 204.77 MB / 32.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.14.0 - ~/.nvm/versions/node/v16.14.0/bin/node
    npm: 8.3.1 - ~/.nvm/versions/node/v16.14.0/bin/npm
  Browsers:
    Chrome: 110.0.5481.177
    Firefox: 101.0
    Safari: 16.3
  npmPackages:
    vue: ^3.2.45 => 3.2.47

Any additional comments?

As a side note, part of my motivation for trying the generic Props type parameter in the first place is that I hate the idea of using JS prototypes just to accomplish all I really care about, which is type checking.

I would ALSO love if defineComponent's "Emits" generic type parameter could be second in the list of type parameters instead of way later...because if I'm able to specify both the props and the emits types right there in defineComponents (and if I can opt out of the runtime validation for both), then 💯 🎉

avatar
Mar 5th 2023

In Vue, props always require runtime config. So what you are trying to do is not supported.

In script setup we support what you want in a way, with defineProps, which is a compiler hint that can take a generic type and the compiler generates the runtime props config from it.

avatar
Mar 5th 2023

@LinusBorg I had script setup's defineProps in mind when I tried defineComponent's generic type. I was hoping that defineComponent's generic Props type might be processed the same way as defineProp's generic type.

If that's not possible for some reason, would this be better suited as a feature request to make props runtime validation optional? I truly don't care about that "feature" if the tooling is giving me immediate type validation.

At the very least, if the goal is to require a runtime props definition, I would still consider it a bug that I'm able to set the Props type in the type parameter without any error. At the very least there should be a TS error expecting the passed object to have a props property matching the type passed as the generic type parameter.

avatar
Mar 5th 2023

Also, @LinusBorg I decided to look into the defineComponent typing in apiDefineComponent.ts, and this is the overload that I'm ending up in by passing the generic type without a runtime value. The fact that I'm in the "no props" case along with the comment "uses user defined props interface" seems to indicate that there is, in a way, partial support for typing props without a runtime value?

// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function defineComponent<
  Props = {},
  RawBindings = {},
  D = {},
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = {},
  EE extends string = string,
  I extends ComponentInjectOptions = {},
  II extends string = string
>(
  options: ComponentOptionsWithoutProps<
    Props,
    RawBindings,
    D,
    C,
    M,
    Mixin,
    Extends,
    E,
    EE,
    I,
    II
  >
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>
avatar
Mar 6th 2023

We would have to ask Evan what the exact use case here is. the line about "Vetur and TSX" (we not have Volar as the default IDE extension) sounds like this type is overload is being used by tooling when converting Vue SFCs into a TSX-like representation for TSC support. I could be wrong though.

At the runtime level, there is definitely no support for this whatsoever. When a component has no runtime props definition, all props passed to it end up in attrs, whileprops is an empty object. There's no way around that right now.

There has been some discussion in our RFCs repo about allowing something like that a while ago, but there has been no activity lately:

https://github.com/vuejs/rfcs/discussions/282

avatar
Mar 6th 2023

@LinusBorg Thanks for the RFC link - that does actually look like exactly what would help here.

I think the TS experience with the Options API would be hugely improved if users could type Props and Emits without the runtime validation.