如何在 Vue 中使用 v-bind 和 v-on 绑定自定义命名模型?
Posted
技术标签:
【中文标题】如何在 Vue 中使用 v-bind 和 v-on 绑定自定义命名模型?【英文标题】:How can I use v-bind and v-on to bind on custom named model in Vue? 【发布时间】:2021-12-07 07:04:30 【问题描述】:我正在制作一个 InputWrapper 组件,用于装饰一些 BootstrapVue 输入组件。重点是自动处理给定输入周围的验证状态、消息、样式等(未在下面的示例中显示)。
我想动态“转发”v-model。当被包装的组件使用自定义模型属性和更新事件进行双向绑定时,就会出现问题。
主要思想如下。
InputWrapper.Vue
<template>
<div>
<slot v-bind="wrappedBindings"></slot>
</div>
</template>
<script>
export default
props:
value: required: true
,
methods:
onInput($event)
this.$emit('input', $event);
,
computed:
wrappedBindings()
return
attrs:
value: this.value
,
on:
input: $event => this.onInput($event),
'update:value': $event => this.onInput($event)
</script>
用法
<div>
<input-wrapper v-model="selectModel" v-slot="attrs, on">
<!-- v-model of b-form-select is :value and @input so this works -->
<b-form-select v-bind="attrs" v-on="on" ...other stuff...></b-form-select>
</input-wrapper>
<input-wrapper v-model="inputModel" v-slot="attrs, on">
<!-- v-model of b-form-input is :value and @update (not @update:value or @input) so this does not work -->
<b-form-input v-bind="attrs" v-on="on" ...other stuff...></b-form-input>
</input-wrapper>
<input-wrapper v-model="checkModel" v-slot="attrs, on">
<!-- v-model of b-form-checkbox is :checked (not :value) and @input so this does not work -->
<b-form-checkbox v-bind="attrs" v-on="on" ...other stuff...></b-form-checkbox>
</input-wrapper>
</div>
我目前不满意的解决方案
<div>
<input-wrapper v-model="inputModel" v-slot="attrs, on">
<b-form-input v-bind="attrs" v-on="on" @update="on.input"
...other stuff...></b-form-input>
</input-wrapper>
<input-wrapper v-model="checkModel" v-slot="attrs, on">
<b-form-checkbox v-bind="attrs" v-on="on" :checked="attrs.value"
...other stuff...></b-form-checkbox>
</input-wrapper>
</div>
此解决方案允许我做我想做的事,但实施时间更长,而且您总是需要附近的 BootstrapVue 文档。
另一种解决方案是为每个 BsVue 输入创建一个自定义组件,但我还需要将每个属性和事件转发到自定义组件。不这样做的原因有很多,但主要是更难维护。
我的问题如下:如何在事先不知道的情况下使用 v-bind="attrs" 和 v-on="on" 动态绑定任何自定义 v-model 属性和事件?
【问题讨论】:
【参考方案1】:这个不容易……
如何在事先不知道的情况下使用 v-bind="attrs" 和 v-on="on" 动态绑定任何自定义 v-model 属性和事件?
你不能。
我的第一个想法是以某种方式访问 Vue 2 中使用的 model option 来自定义该组件上 v-model
的道具名称和事件名称。但不幸的是,这在$scopedSlots.default()
上无法访问(并且以这种方式使用它无论如何都会非常无效)
恕我直言,最好的选择是在开槽组件上使用v-model
,让 Vue 为您完成繁重的工作......但是
..通常当您创建input
(或一些自定义输入)包装器时,最简单的方法是使用computed
将内部组件的v-model
“连接”(或转发)到v-model
包装器:
<template>
<input :type="type" v-model="model" />
</template>
<script>
export default
props: ['value', 'type'],
computed:
model:
get() return this.value
set(newValue) this.$emit('input', newValue)
</script>
为什么?出于完全相同的原因,你问你的问题。您无需选择使用哪个道具和事件(因为不同的input
类型为value
使用不同的道具名称并使用不同的事件) - 您可以将其留给v-model
但是在开槽组件上做同样的事情有点棘手。您不能直接在<slot>
上放置v-model
。它必须放在开槽组件上(在父模板中)。所以唯一的方法是将上面计算的“东西”传递给插槽道具。这也是个问题。
-
插槽道具不能被插槽内容改变,就像普通道具不应该从组件内部改变一样
不可能将
v-bind
作为一个整体计算到一个槽。如果您尝试这样做,开槽组件仅接收从 getter 读取的当前值,而 setter 则被抛在后面...
为了克服第一个问题,可以使用与普通 props 相同的技术 - 而不是只传递一个值,而是传递一个对象并将该值作为其属性。现在您可以根据需要更改该属性的值(这被许多人认为是肮脏的,但我认为它非常强大,如果使用得当,可以为您节省大量样板代码)
第二个问题可以通过使用 Object.defineProperty API 来解决,它允许您执行类似于 Vue computed
属性的操作 - 定义对象的属性并声明要在读取或写入属性时使用的您自己的函数。
这是一个最终的解决方案:
// InputWrapper.vue
<template>
<div>
<slot v-bind="wrappedBindings"></slot>
</div>
</template>
<script>
export default
props:
value: required: true ,
,
computed:
wrappedBindings()
const self = this;
const bindings =
attrs:
,
on:
,
model: ,
;
// property MUST be on 2nd level otherwise it wont work thanks to how Vue 2 is implemented
// https://github.com/vuejs/vue/blob/0948d999f2fddf9f90991956493f976273c5da1f/src/core/instance/render-helpers/render-slot.js#L24
// https://github.com/vuejs/vue/blob/0948d999f2fddf9f90991956493f976273c5da1f/src/shared/util.js#L205
Object.defineProperty(bindings.model, "proxy",
get()
return self.value;
,
set(newValue)
self.$emit("input", newValue);
,
enumerable: true,
configurable: false,
);
return bindings;
,
,
;
</script>
及用法:
<input-wrapper v-model="inputModel" v-slot=" attrs, on, model ">
<b-form-input
v-bind="attrs"
v-on="on"
v-model="model.proxy"
></b-form-input>
</input-wrapper>
这当然不理想(尤其是您需要在v-model
中使用model.proxy
)但现在我看不到任何其他方式来实现这种“通用”包装器(当然除了为自定义组件上的v-model
选择一个坚持“值”/“输入”的组件库)
Demo
【讨论】:
非常感谢您花时间发布完整的答案!很遗憾我们不得不求助于一些 hacky 的东西,但至少生成的 API 感觉还不错。如果我可以两次投票给你,我会的。 免责声明:这没有用 Vue 3 测试,所以“可能有龙”。我会考虑不同的实现——而不是一个通用包装器,而是创建多个“基础”组件——每个输入类型一个。共享行为可以实现为 mixin(或组合函数——对我来说是首选方式)。作为奖励,使用这些输入的组件(模板)也将更加简单......以上是关于如何在 Vue 中使用 v-bind 和 v-on 绑定自定义命名模型?的主要内容,如果未能解决你的问题,请参考以下文章