如何使用 v-bind 和 v-on 绑定 Vue 中的自定义命名模型?
How can I use v-bind and v-on to bind on custom named model in Vue?
我正在制作一个 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 属性和事件?
这个不容易...
How can I use v-bind="attrs" and v-on="on" to dynamically bind any custom v-model attribute and event without knowing them beforehand?
你不能。
我的第一个想法是以某种方式达到 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
但是在开槽组件上做同样的事情有点棘手。您不能将 v-model
直接放在 <slot>
上。它必须放置在开槽组件上(在父模板中)。所以唯一的方法是将像上面计算的“东西”传递到插槽道具中。那也是问题。
- slot props 不能被 slot 内容改变,就像普通 props 不应该从组件内部改变一样
- 不可能
v-bind
将 作为一个整体 计算到插槽中。如果你尝试这样做,开槽组件只接收从 getter 读取的当前值,而 setter 被留在后面......
为了克服第一个问题,可以使用与普通道具相同的技术 - 而不是只传递一个值,而是传递一个对象并将该值设为 属性。现在你可以随心所欲地改变 属性 的值(许多人认为这很脏,但我认为它非常强大,如果使用得当,可以节省大量样板代码)
第二个问题可以通过使用 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
的“值”/“输入”)
我正在制作一个 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 属性和事件?
这个不容易...
How can I use v-bind="attrs" and v-on="on" to dynamically bind any custom v-model attribute and event without knowing them beforehand?
你不能。
我的第一个想法是以某种方式达到 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
但是在开槽组件上做同样的事情有点棘手。您不能将 v-model
直接放在 <slot>
上。它必须放置在开槽组件上(在父模板中)。所以唯一的方法是将像上面计算的“东西”传递到插槽道具中。那也是问题。
- slot props 不能被 slot 内容改变,就像普通 props 不应该从组件内部改变一样
- 不可能
v-bind
将 作为一个整体 计算到插槽中。如果你尝试这样做,开槽组件只接收从 getter 读取的当前值,而 setter 被留在后面......
为了克服第一个问题,可以使用与普通道具相同的技术 - 而不是只传递一个值,而是传递一个对象并将该值设为 属性。现在你可以随心所欲地改变 属性 的值(许多人认为这很脏,但我认为它非常强大,如果使用得当,可以节省大量样板代码)
第二个问题可以通过使用 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
的“值”/“输入”)