如何在 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

但是在开槽组件上做同样的事情有点棘手。您不能直接在&lt;slot&gt; 上放置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 绑定自定义命名模型?的主要内容,如果未能解决你的问题,请参考以下文章

vue v-bind v-on

vue v-bind v-on

vue监听事件 v-on

vue学习——v-on:

v-bind v-on

vue-常用指令&条件渲染&列表渲染