|
| 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