Subscribe on changes!

Support for using discriminated union props with `defineComponent` and runtime props

avatar
Jun 16th 2023

Vue version

3.3.4

Link to minimal reproduction

https://github.com/justin-schroeder/vue-3.3-prop-unions

Steps to reproduce

Clone the repository and install:

git clone git@github.com:justin-schroeder/vue-3.3-prop-unions.git
cd vue-3.3-prop-unions.git
pnpm install
pnpm build

What is expected?

There are 2 ideas to highlight here:

TypeScript

The example defines a component using the new function syntax for defineComponent with a discriminated union as the prop type. Each union type has 1 prop the other type does not have (bar vs foo). The runtime props include both bar and foo to ensure they are both accepted at runtime.

import { defineComponent, h } from "vue"
import type { RenderFunction } from "vue"

type Pizza = { type: "pizza"; toppings: "pepperoni" | "pineapple"; bar: string }
type Pie = { type: "pie"; toppings: "apple" | "cherry"; foo: string }
type PropUnion = Pizza | Pie

export default defineComponent(
  function setup<P extends PropUnion>(props: P): RenderFunction {
    return () => h("div", `${props.type} with ${props.toppings}`)
  },
  {
    props: ["type", "toppings", "foo", "bar"],
  }
)

I would expect this to work properly since the runtime props are inclusive of all unions, there is no overload that allows for the runtime props to differ from the union props even if the runtime props are only defining additional props. Volar is actually ok with this typing, but Vue itself does not have a matching component overload. For example:

image

Adding an additional overload for defineComponent seems like it may sufficiently address this.

Runtime prop proposal

Alternatively, it would be ideal to allow for runtime props to be determined at runtime. This seems like it would be primary beneficial to library authors, so perhaps no SFC compatibility is necessary. One option:

// Signature:
DefineComponent<P, E...>(setup<P>(props: P, context: SetupContext<E, S>): RenderFunction, options?: Omit<ComponentOptions, 'props'> & { props: (allAttrs: Record<string, any>): P }): (props: P & EmitsToProps<E>) => any

defineComponent(
  function setup<P>(props: P) {
   //...
  },
 {
   props (allAttrs: Record<string, any>): P /* <-- actual runtime props */  {
    if (allAttrs.type === 'foo') {
      return ['a', 'b', 'c']
    }
    return ['d', 'e', 'f']
   }
 }
)

What is actually happening?

No overload matches this call

image

System Info

System:
    OS: macOS 13.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 84.44 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.14.2 - /usr/local/bin/node
    Yarn: 1.22.18 - ~/.yarn/bin/yarn
    npm: 9.5.0 - /usr/local/bin/npm
  Browsers:
    Brave Browser: 114.1.52.126
    Chrome: 114.0.5735.133
    Edge: 114.0.1823.51
    Firefox: 111.0.1
    Safari: 16.2
  npmPackages:
    vue: ^3.3.4 => 3.3.4

Any additional comments?

No response

avatar
Nov 3rd 2023

duplicate of https://github.com/vuejs/core/issues/9335

Closing this one, because the other one has a PR attached.