Skip to content

Commit 83d8780

Browse files
authored
Component v-model API change (#31)
1 parent 8b8288b commit 83d8780

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
- Start Date: 2019-04-09
2+
- Target Major Version: 3.x
3+
- Reference Issues: vuejs/rfcs#8
4+
- Implementation PR: N/A
5+
6+
# Summary
7+
8+
Adjust `v-model` API when used on custom components.
9+
10+
This builds on top of #8 (Replace `v-bind`'s `.sync` with a `v-model` argument).
11+
12+
# Basic example
13+
14+
# Motivation
15+
16+
Previously, `v-model="foo"` on components roughly compiles to the following:
17+
18+
``` js
19+
h(Comp, {
20+
value: foo,
21+
onInput: value => {
22+
foo = value
23+
}
24+
})
25+
```
26+
27+
However, this requires the component to always use the `value` prop for binding with `v-model` when the component may want to expose the `value` prop for a different purpose.
28+
29+
In 2.2 we introduced the `model` component option that allows the component to customize the prop and event to use for `v-model`. However, this still only allows one `v-model` to be used on the component. In practice we are seeing some components that need to sync multiple values, and the other values have to use `v-bind.sync`. We noticed that `v-model` and `v-bind.sync` are fundamentally doing the same thing and can be combined into a single construct by allowing `v-model` to accept arguments (as proposed in #8).
30+
31+
# Detailed design
32+
33+
In 3.0, the `model` option will be removed. `v-model="foo"` (without argument) on a component compiles to the following instead:
34+
35+
``` js
36+
h(Comp, {
37+
modelValue: foo,
38+
'onUpdate:modelValue': value => (foo = value)
39+
})
40+
```
41+
42+
If the component wants to support `v-model` without an argument, it should expect a prop named `modelValue`. To sync its value back to the parent, the child should emit an event named `"update:modelValue"` (see [Render Function API change](https://github.com/vuejs/rfcs/blob/render-fn-api-change/active-rfcs/0000-render-function-api-change.md) for details on the new VNode data structure).
43+
44+
The default compilation output prefixes the prop and event names with `model` to avoid conflict with common prop names.
45+
46+
RFC #8 proposes the ability for `v-model` to accept arguments. The argument can be used to denote the prop `v-model` should bind to. `v-model:value="foo"` compiles to:
47+
48+
``` js
49+
h(Comp, {
50+
value: foo,
51+
'onUpdate:value': value => (foo = value)
52+
})
53+
```
54+
55+
In this case, the child component expects a `value` prop and emits `"update:value"` to sync.
56+
57+
Note that this enables multiple `v-model` bindings on the same component, each syncing a different prop, without the need for extra options in the component:
58+
59+
``` html
60+
<InviteeForm
61+
v-model:name="inviteeName"
62+
v-model:email="inviteeEmail"
63+
/>
64+
```
65+
66+
## Handling Modifiers
67+
68+
In 2.x, we have hard-coded support for modifiers like `.trim` on component `v-model`. However, it would be more useful if the component can support custom modfiers. In v3, modifiers added to a component `v-model` will be provided to the component via the `modelModifiers` prop:
69+
70+
```html
71+
<Comp v-model.foo.bar="text" />
72+
```
73+
74+
Will compile to:
75+
76+
``` js
77+
h(Comp, {
78+
modelValue: text,
79+
'onUpdate:modelValue': value => (text = value),
80+
modelModifiers: {
81+
foo: true,
82+
bar: true
83+
}
84+
})
85+
```
86+
87+
For `v-model` with arguments, the generated prop name will be `arg + "Modifiers"`:
88+
89+
```html
90+
<Comp
91+
v-model:foo.trim="text"
92+
v-model:bar.number="number" />
93+
```
94+
95+
Will compile to:
96+
97+
``` js
98+
h(Comp, {
99+
foo: text,
100+
'onUpdate:foo': value => (text = value),
101+
fooModifiers: { trim: true },
102+
bar: number,
103+
'onUpdate:bar': value => (bar = value),
104+
barModifiers: { number: true },
105+
})
106+
```
107+
108+
## Usage on Native Elements
109+
110+
Another aspect of the `v-model` usage is on native elements. In 2.x, the compiler produces different code based on the element type `v-model` is used on. For example, it outputs different prop/event combinations for `<input type="text">` and `<input type="checkbox">`. However, this strategy does not handle dynamic element or input types very well:
111+
112+
``` html
113+
<input :type="dynamicType" v-model="foo">
114+
```
115+
116+
The compiler has no way to guess the correct prop/event combination at compile time, so it has to produce [very verbose code](https://template-explorer.vuejs.org/#%3Cinput%20%3Atype%3D%22foo%22%20v-model%3D%22bar%22%3E) to cover possible cases.
117+
118+
In 3.0, `v-model` on native elements produces the exact same output as when used on components. For example, `<input v-model="foo">` compiles to:
119+
120+
``` js
121+
h('input', {
122+
modelValue: foo,
123+
'onUpdate:modelValue': value => {
124+
foo = value
125+
}
126+
})
127+
```
128+
129+
The module responsible for patching element props for the web platform will then dynamically determine how to actually apply them. This enables the compiler to output much less verbose code.
130+
131+
# Drawbacks
132+
133+
TODO
134+
135+
# Alternatives
136+
137+
N/A
138+
139+
# Adoption strategy
140+
141+
TODO
142+
143+
# Unresolved questions
144+
145+
## Usage on Custom Elements
146+
147+
Reference: [vuejs/vue#7830](https://github.com/vuejs/vue/issues/7830)
148+
149+
In 2.x it is difficult to use `v-model` on native custom elements, because the compiler can't tell a native custom element from a normal Vue component (`Vue.config.ignoredElements` is runtime only). The result is that given a custom element with `v-model`:
150+
151+
``` html
152+
<custom-input v-model="foo"></custom-input>
153+
```
154+
155+
The 2.x compiler produces [code for a Vue component](https://template-explorer.vuejs.org/#%3Ccustom-input%20v-model%3D%22foo%22%3E%3C%2Fcustom-input%3E) instead of the native default `value/input` pair.
156+
157+
In 3.0, the compiler will produce exactly the same code for both Vue components and native elements, and a native custom element will be handled properly as a native element.
158+
159+
The remaining question is that 3rd party custom elements could have unknown prop/event combinations and do not necessarily follow Vue's sync event naming conventions. For example if a custom element expects to work like a checkbox, Vue has no information on the property to bind to or the event to listen to. One possible way to deal with this is to use the `type` attribute as a hint:
160+
161+
``` html
162+
<custom-input v-model="foo" type="checkbox"></custom-input>
163+
```
164+
165+
This would tell Vue to bind `v-model` using the same logic for `<input type="checkbox">`, using `checked` as the prop and `change` as the event.
166+
167+
If the custom element doesn't behave like any existing input type, then it's probably better off to use explicit `v-bind` and `v-on` bindings.

0 commit comments

Comments
 (0)