原文链接 https://github.com/vuejs/rfcs/blob/master/active-rfcs/0011-v-model-api-change.md

  • 开始时间:2019-04-09
  • 目标主要版本: 3.x
  • 引用 issue:vue/rfcs#8
  • 实现的 PR:N/A

摘要

调整在自定义组件上使用 v-model API。

这建立在 #8(用 v-model 参数替换 v-bind 上的 .sync)之上。

基本范例

动机

在之前,组件上的 v-model=”foo” 大致可以编译为一下内容:

  1. h(Comp, {
  2. value: foo,
  3. onInput: value => {
  4. foo = value
  5. }
  6. })

然而,这要求组件总是使用 value 属性来于 v-model 绑定,而组件可能想为不同目的暴露 value 属性。

在 2.2 中,我们引入了 model 组件选项,允许组件给 v-model 自定义 propevent。然而,这仍然只允许在组件上使用 v-model。在实践中,我们看到很多组件需要多个同步值,而其他值必须使用 v-bind.sync。我们注意到 v-model 和 v-bind.sync 从根本上说是同一回事,可以允许 v-model 接受参数(如 #8 建议的那样)合并成一个结构。

具体设计

在 3.0 中,model 选项将会被移除。v-model=”foo”(不带参数)在一个组件上的编译结果如下:

  1. h(Comp, {
  2. modelValue: foo,
  3. 'onUpdate:modelValue': value => (foo = value)
  4. })

如果组件想要支持没有参数的 v-model,它应该期望一个名为 modelValue 的 prop。为了把它的值同步到父元素,子元素将会触发一个 “update:modelValue” 的事件(关于新的 VNode 数据结构的细节,请看 Render Function API change)。

默认的编译输出会在 prop 和 event 名称前加上 model,以避免于普通 prop 的名称冲突。

RFC #8 提出了 v-model 接受参数的能力。参数可以用来表示 v-model 应该绑定 prop。v-model:value=”foo” 应该编译成:

  1. h(Comp, {
  2. value: foo,
  3. 'onUpdate:value': value => (foo = value)
  4. })

在这种情况下,子组件期望一个 value 属性,并且触发 "update:value" 来进行同步。

注意,这可以在同一个组件上实现多个 v-model 绑定,每一个同步一个不同的 prop,而不需要在组件中设置额外的选项:

  1. <InviteeForm
  2. v-model:name="inviteeName"
  3. v-model:email="inviteeEmail"
  4. />

处理修饰符

在 2.x 中,我们对组件 v-model 上的 .trim 等修饰符进行了硬编码支持。然而,如果组件可以支持自定义的修饰符,那就更有用了。在 v3 中,添加到组件 v-model 的修饰符将通过 modelModifiers 属性提供给组件:

  1. <Comp v-model.foo.bar="text" />

将会编译成:

  1. h(Comp, {
  2. modelValue: text,
  3. 'onUpdate:modelValue': value => (text = value),
  4. modelModifiers: {
  5. foo: true,
  6. bar: true
  7. }
  8. })

对于带有参数的 v-model,生成的 prop 名字将是 arg + “Modifiers”:

  1. <Comp
  2. v-model:foo.trim="text"
  3. v-model:bar.number="number" />

将会编译成:

  1. h(Comp, {
  2. foo: text,
  3. 'onUpdate:foo': value => (text = value),
  4. fooModifiers: { trim: true },
  5. bar: number,
  6. 'onUpdate:bar': value => (bar = value),
  7. barModifiers: { number: true },
  8. })

使用原生元素

v-model 使用的另一个方面是在原生元素上。在 2.x 中,编译器会根据 v-model 使用的元素类型产生不同的代码。例如,它为 输出不同的属性和事件。然而,这种策略不能很好的处理动态元素和 input 类型:

  1. <input :type="dynamicType" v-model="foo">

编译器没有办法在编译时猜测正确的属性和事件组合,所以它必须产生非常冗长的代码来涵盖所有有可能的情况。

在 3.0 中,v-model 在原生元素上产生的输出与使用组件时完全相同。例如, 编译为:

  1. h('input', {
  2. modelValue: foo,
  3. 'onUpdate:modelValue': value => {
  4. foo = value
  5. }
  6. })

然后,负责为 web 平台 patch 元素属性的模块将动态的决定如何实际使用它们。这使得编译器能够输出更少的冗长的代码。

缺点

TODO

备选方案

N/A

采纳策略

TODO

没有解决的问题

引用:vuejs/vue#7830

在 2.x 中,很难在原生自定义元素上使用 v-model,因为编译器无法区分原生自定义元素和普通的 Vue 组件(Vue.config.ignoredElements 仅在运行时)。其结果就是给定一个带有 v-model 的自定义元素。

  1. <custom-input v-model="foo"></custom-input>

2.x 的编译器产生了 Vue 组件代码,而不是原生默认 value/input 对。

在 3.0中,编译器将为 Vue 组件和原生元素产生完全相同的代码,并且原生自定义组件将会作为原生元素被正确处理。

剩下的问题是,第三方自定义元素可能有未知的属性和事件组合,不一定遵循 Vue 的同步事件命名惯例。例如,如果一个自定义元素希望像复选框一样工作,Vue 就没有关于要绑定的属性或者监听的事件的信息。处理这个问题的一个可能的方法就是使用 type 特性作为一个提示:

  1. <custom-input v-model="foo" type="checkbox"></custom-input>

这将告诉 Vue 使用与 <input type="checkbox"> 相同的逻辑来绑定v-model,使用 checked 作为属性,chang 作为事件。

如果自定义元素的行为不像任何现有的输入类型,那么最好使用显式的 v-bindv-on 绑定。