Description
The transparent wrapper pattern is very convenient and increasingly common, but still a little clunky in Vue 2.x. For example, to get a BaseInput
component that can be used exactly like a normal input
element, we need to do this:
<script>
export default {
props: {
value: {
type: String
}
},
computed: {
listeners() {
return {
...this.$listeners,
input: event => {
this.$emit('input', event.target.value)
}
}
}
}
}
</script>
<template>
<input
:value="value"
v-on="listeners"
>
</template>
In Vue 3, that will currently translate to:
<script>
class InputText extends Component {
// Currently, `v-model` on a component assumes `value` is a
// prop, so it will never be included in $attrs, even if no
// `value` prop is defined. This forces users to always define
// `value` as a prop, even if it's never needed by the component.
static props = {
value: String
}
get attrs () {
return {
...this.$attrs,
// We have to manually include the `value` prop to `attrs`,
// due to `v-model`'s assumption that `value` is a prop.
value: this.value,
// Since `v-model` listens to the `input` event by default,
// the user is forced to override it when using binding
// $attrs in order to prevent the unwanted behavior of the
// `event` object being emitted as the new value.
onInput: event => {
this.$emit('input', event.target.value)
}
}
}
}
</script>
<template>
<input v-bind="attrs">
</template>
Still quite a bit of work. 😕
However, if we remove v-model
's assumption that value
is a prop, then it no longer has to be added separately. And if we also make v-model
listen for an update:value
event by default (as @posva suggested here), instead of input
, then we no longer have to override input
and can just add a new listener.
With these 2 changes, our BaseInput
component would simplify to:
<template>
<input
v-bind="$attrs"
@input="$emit('update:value', $event.target.value)"
>
</template>
To me, this is much more convenient and intuitive. 🙂
@yyx990803 @posva @johnleider What do you think?