- Start Date: 2020-03-05
- Target Major Version: 3.x
- Reference Issues: (fill in existing related issues, if any)
- Implementation PR: (leave this empty)
Add a prop
built-in modifier to v-model
in order to support two-way binding for props using events.
With a prop
modifier you'd able to create transparent v-model
wrappers without extra hassle:
<Foo v-model.prop="value" />
Is equivalent to:
<Foo :value="value" @input="$emit('input', $event)" />
Named models are also supported:
<Foo v-model:bar.prop="bar" />
Which is equivalent to:
<Foo :bar="bar" @update:bar="$emit('update:bar', $event)" />
Creating wrapper components around anything that uses v-model
could be considered a somewhat cumbersome process at the moment.
Firstly, you'll have to know what you're dealing with when using v-model
: an html element or a component.
It's important to distinguish between those because built-in inputs for example already have a custom behaviour for v-model
.
In order to preserve it you'll have to use a computed v-model
:
export default {
computed: {
model: {
get() { return this.value }
set(value) { this.$emit('input', value) }
}
}
}
Simply using @input="$emit('value', $event)"
on <input>
for example won't work because you'll be working with a DOM event, that's not processed by Vue's built-in model.
You'll have to do this for each html element that has built-in v-model
processing done by Vue (these include: <input type="text">
, <input type="checkbox">
, <input type="radio">
, <textarea>
and <select>
).
With a prop
modifier there's no such an issue anymore. You simply write v-model.prop="value"
and the binding is created automatically using events. There's no longer any need to distinguish between html elements and components.
Secondly, you'll be free of creating model wrappers manually for components, so this code is no longer needed:
<Foo :value="value" @input="$emit('input', $event)" />
And can be replaced with a single v-model
:
<Foo v-model.prop="value" />
With the introduction of arguments in v-model
there's also a problem with wrapping components that expect you to use v-model
with arguments.
Consider this example:
<Foo v-model:bar="bar" />
In case we're wrapping such a component using props the code will look as following:
<Foo :bar="bar" @update:bar="$emit('update:bar', $event)" />
This is not very user-friendly compared to simply v-model:bar="bar"
when dealing with local state.
Wrapper components should not introduce extra barriers when working with v-model
.
One may consider having simply v-model
to deal with both local state binding and event binding.
Modifier is required in order to solve two problems with this approach:
- Performance penalty of checking against props
- Readability (not being able to distinguish between data that's local and external)
prop
modifier changes v-model
behaviour.
When applied, v-model
should no longer mutate model value, but instead emit an event that corresponds to model name with a new model value.
Provided we have such a template:
<Foo v-model:bar.prop="bar" />
Would compile to this render function:
export default {
render() {
h('Foo', {
bar: this.bar,
'onUpdate:bar': (value) => { this.$emit('update:bar', value) },
barModifiers: {
prop: true
}
})
}
}
prop
modifier should also gracefully handle interaction with built-in v-model
directives such as vModelSelect
, vModelText
, vModelCheckbox
and vModelRadio
that are done on a compiler level.
prop
modifier is also conflict-free for any other model modifiers, except for prop
modifier itself.
prop
modifier does not support model value as a computed property with getter and setter. An error should be raised in that case.
<template>
<input v-model.prop="computedModel">
</template>
<script>
export default {
computed: {
computedModel: {
// should raise an error on component render
get() {},
set() {},
}
}
}
</script>
Deep models should return a cloned value with a model value property replaced.
<template>
<input v-model.prop="modelValue.title">
</template>
<script>
export default {
props: ['modelValue'],
}
</script>
In the example above v-model
setter should do the following:
set(titleValue) {
const newValue = Object.assign({}, this.modelValue);
newValue.title = titleValue;
this.$emit('update:modelValue', newValue);
}
For arrays it should be:
<template>
<input v-model.prop="modelValue[1]">
</template>
<script>
export default {
props: ['modelValue'],
}
</script>
set(titleValue) {
const newValue = this.modelValue.slice(0);
newValue[0] = titleValue;
this.$emit('update:modelValue', newValue);
}
A new reserved modifier for model.
There's already a prop
modifier for v-bind
which serves a completely different task.
Having two modifiers with a same name could result in confusion.
An alternative event
modifier name could be considered to better indicate a type of binding we're using.
No migration steps required.