Subscribe on changes!

Improve the props parameter in setup function

avatar
Feb 23rd 2021

What problem does this feature solve?

import { defineComponent, toRefs, watch } from "vue";

export default defineComponent({
  props: {
    foo: Boolean
  },
  setup(props) {
    const { foo } = toRefs(props);

    // As `foo` might be undefined instead of a Ref
    // we cannot watch it directly like this
    watch(foo, () => { ... }); 
  }
});

Instead, we need to use:

watch(() => props.foo, () => { ... });

So we need to switch to props.foo whenever we need to watch it, which doesn't feel very natural to me.

What does the proposed API look like?

For all declared props, always provide { [propName]: undefined } in the props parameter of the setup function so that we can write:

setup(props) { // props being { foo: undefined }
  const { foo } = toRefs(props);

  // foo is Ref
  watch(foo, () => { ... }); 
}
avatar
Feb 23rd 2021

How would you differentiate a prop passed as undefined from a missing prop?

Wouldn't make more sense to do:

const foo = toRef(props, 'foo')

Edit: Although this is technically a breaking change, having the key with undefined seems to be more flexible, especially with toRefs() (which otherwise breaks). The limitation would be not being able to detect if a prop is missing when undefined is an allowed value (not Booleans basically). e.g. if the user is passing undefined, which I don't think one should do because they should be passing null instead, to tell the component the prop is empty

avatar
Feb 23rd 2021

If a prop is missed, it will not appear in props:

{
  props: {
    foo: {
      type: String
    }
  },
  setup(props) {
    console.log(Object.keys(props)) // []
    const { foo } = toRefs(props) // foo is `undefined`
  }
}

So I think it is as expected

avatar
Feb 23rd 2021

How would you differentiate a prop passed as undefined from a missing prop?

@posva Hmmm this is a valid point. Maybe we do need to use toRef on individual prop instead as you suggested.

@HcySunYang I think @posva is right if we always provide undefined for missing prop there will be no way to tell if it comes from users.


So maybe we should only use toRefs to convert required props (or with default values) to refs. I'm closing this one 😄

avatar
Mar 30th 2021

How would you differentiate a prop passed as undefined from a missing prop?

What would be the use case for an optional prop that wasn't passed in to the component?

<my-comp/>
<my-comp :foo="undefined"/>

<script>
props : {
 foo: String
}
</script>

That code should behave the same.


This behaviour gets really inconsistent when you use a Boolean instead of a String example


This behaviour is different from v2 options API with options API the properties will always be defined. If this is intended it needs to be marked as breaking change from v2

// vue2
{
  props: {
    foo: String
  },

  data(){
    return {
      hasKey: 'foo' in this.$props // true
    }
  }
}

// vue3
{
  props: {
    foo: String
  },

  data(){
    return {
      hasKey: 'foo' in this.$props // false
    }
  }
}

Altho I still dont know the use case for handling an optional property without default (aka undefined) differently from undefined

avatar
Mar 30th 2021

After some discussion with @pikax I think this issue should be reopened. As with current implementation, we actually cannot reliably detect missing props with a simple key in props check, while improving the experience of toRefs may bring more benefits.

avatar
May 8th 2021

This did break prop pass detection that I was relying on as reported in https://github.com/logaretm/vee-validate/pull/3294

As a workaround I had introduced a symbol as a default value to detect when the user didn't pass the prop:

export const EMPTY_VALUE = Symbol('Default empty value');
``

```js
{
  modelValue: {
    type: null,
    default: EMPTY_VALUE,
  },
}

This should probably be marked as a breaking change in the changelog.