Sometimes it is needed to build complex fill-in forms depending on multiple objects, that could be changed inside the component and should be updated at parent component.
Example of possible use (there are many such cases in complex graph-based single page apps):
<oreder-card v-model:profile="user.profile" v-model:order="order"></order-card>
Current approach:
<oreder-card :profile="user.profile" v-on:input_profile="user.profile = arguments[0]" :order="order" v-on:input_order="order = arguments[0]"></order-card>
Component v-model
is designed for single value input components that attend to similar use cases for native input elements.
For a complex component that manages the synchronization of more than one values, explicit prop/event pairs is the proper solution. In this particular case I don't think the saved keystrokes are worth the added complexity of additional syntax.
@yyx990803
If we have a Dialog
component, the current prop/event pairs looks like this:
<dialog :open="messageOpen" @close="messageOpen = $event.value">
It's just one prop, we'll have much more for a rather complicated component.
Let's look at how component libraries are doing this right now.
Element and iView usually use props
to initialize a data
field (like .once
) and then watch
some of the props
to update local data
and to respond when parent component's data updates (like props
without immutability). Child components may $emit
various events to notify some props have been changed and request their parents to update data. This works but parent component's data may be inconsistent with the child's view unless the prop change event is handled properly, and the binding syntax is explicit but redundant, comparing to .sync
.
For some components, there might be a "major" prop which covers most of child component's logic. eg. For Dialog
s, this prop would be open
. In such cases the "major" prop may be designed to comply with the v-model
interface thus parent components can bind v-model
to it. As you said, v-model
is designed for single value components but as you can see, people are actually using it as a restricted version of .sync
(works for only one prop).
Regarding to Vue's current design, I'd say is not proper but the most popular UI libraries are using this paradigm. People use v-model
on non-input components just because they don't have .sync
.
If only prop/event pairs are the only proper way, then why v-model
is designed in the first place? Just because it has a "major" prop which matches scenarios like form validation/submission?
I understand why .sync
is removed but I don't think it's a good idea for a component library to introduce solutions like vuex
because this will restrict the way how people use it.
Under the premises that .sync
won't come back and v-model
is designed for single-value inputs, I'd ask if it is proper to let custom directives to do this. For example, we design custom directives like this:
<dialog v-sync:open="messageOpen">
Which works as syntactic sugar for
<dialog :open="messageOpen" @propchange="dialogUpdate">
methods: {
dialogUpdate(prop, value) {
if (prop === 'open') {
this.messageOpen = value
}
}
}
@Justineo I think this is more like a compile-time de-sugaring. It's actually possible by adding custom compilation modules to vue-loader
, but the drawback is your components can then only be compiled with the extra build configuration.
Alternatively, we can bring .sync
back but instead of implicit two-way binding it also de-sugar into prop/listener pairs...
I think bringing .sync
back as sugar will already please most library developers. AFAIK quite a few Vue developers are stuck with 1.x
just because they rely on .sync
so much. For me we are now developing a UI library and I'm looking into how others are solving this problem. The paradigm most libs are using seems weird to me but they have no choice.
Even if we cannot bring back .sync
directly, I still hope custom directives or something like this can provide similar feature. But right now it seems not possible to replace a prop/event listener pair with a custom directive (prop bindings are already compiled into the render function and we have no runtime API to add new ones).
@QingWei-Li @icarusion what are your opinions on this? Do you run into this problem often?
We have had the same problem. Actually, we have implement v-sync
directive, but not good. I think it's a good idea if we can have built-inv-sync
directive. @Leopoldthecoder What do you think?
I admit using v-model
in a non-form component is a bit strange.
When designing Element's Dialog
, at first we tried to bring back .sync
by implementing a custom v-sync
directive, so that we can
<dialog v-sync:visible="myVisible">
But it couldn't work because a component is not allowed to modify its own props.
Then there is prop/listener pair. The disadvantage of this approach is that the user has to write an event listener to manage his data. We decided it didn't make much sense for a component library.
So finally we chose v-model
. It kinda breaks the semantics but perfectly does the job.
Generally we didn't run into this problem quite often when developing Element, perhaps just Dialog
and Pagination
. But having a built-in v-sync
that compiles to prop/listener pairs seems really handy. I'd be happy to see it happening.
The primary reason for removal of the old .sync
is it encourages the child to implicitly affect parent state. But if we can ensure that every time the child affects parent state, it's always by explicitly emitting an event, then we can bring back .sync
in the form below:
<test :foo.sync="bar" />
is expanded into the following at compile-time:
<test :foo="bar" @update:foo="value => bar = value" />
So the parent doesn't need to define a method just to do the mutation, but the child needs to emit an event instead of just mutating the prop:
this.$emit('update:foo', newValue)
Since this is encapsulated inside the child implementation, consumers of the library can just use .sync
like the old days.
This looks great. Can't wait to see it back. 👍
Nice!
@jingsam
@yyx990803
this.$emit('update:foo', newValue)
不明白怎么用这行代码,在哪里调用?有没有完整的例子看看
@binsinger 就是在组件里需要更新 foo
的地方触发一个事件,用法看文档
In Angular, We can use [()] to implement to way bindings ignore bindings count. May be it will be awesome If vue offer this feature
Vue has this feature, it's the sync modifier on v-bind
More info about .sync
https://medium.com/front-end-weekly/vues-v-model-directive-vs-sync-modifier-d1f83957c57c
For anyone landing here and wondering: This is available in Vue 2.3+
: https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
Most helpful comment
The primary reason for removal of the old
.sync
is it encourages the child to implicitly affect parent state. But if we can ensure that every time the child affects parent state, it's always by explicitly emitting an event, then we can bring back.sync
in the form below:is expanded into the following at compile-time:
So the parent doesn't need to define a method just to do the mutation, but the child needs to emit an event instead of just mutating the prop:
Since this is encapsulated inside the child implementation, consumers of the library can just use
.sync
like the old days.