如何在 Vue 中显示包裹的 <input>?

How to expose wrapped <input> in Vue?

我正在尝试在 Vue 中创建一个可重复使用的样式输入字段。为了让它有样式(例如里面有一个图标)我需要把它包装在另一个 html-element.

让我们调用下面的例子StyledInput

<div class="hasIcon">
    <input />
    <i class="someIcon"></i>
<div>

如果我想使用 StyledInput 它可能看起来像这样:

<styled-input @keyup.enter="doSomething">
</styled-input>

但这行不通,因为事件侦听器附加到 <div> 而不是 <input>

一种解决方法是从输入字段发出所有按键事件:

<div class="hasIcon">
    <input @keyup="$emit('keyup', $event) />
    <i class="someIcon"></i>
<div>

但这不会很好地扩展,因为每次开发人员使用未映射的道具或事件时都必须重写它。

有没有办法只让内部元素暴露给使用它的人?

我不确定是否有 Vue 方法来实现这一点,因为据我所知,没有办法动态绑定 vue 事件,但是可以使用香草 javascript 通过将所有事件作为道具传递然后使用 addEventListener() 映射它们以添加您的自定义事件:

Vue.component('my-input', {
  template: "#my-input",
  props: ['events'],
  mounted() {
    // get the input element
    let input = document.getElementById('styled-input');

    // map events
    this.events.forEach((event) => {
      let key = Object.keys(event);
      input.addEventListener(key, event[key]);
    });
  }
})

然后你就可以像这样把所有事件作为道具传递:

<my-input :events="events"></my-input>

查看模型:

var app = new Vue({
  el: "#app",
  data: {
    events: [{
      focus: () => {
        console.log('focus')
      }
    }, {
      keyup: (e) => {
        console.log(e.which)
      }
    }]
  }
})

这是 JSFiddle:https://jsfiddle.net/h1dnk40v/

当然,这意味着任何开发人员都必须执行映射键代码等操作,因此您将失去 Vue 提供的一些便利。

我要提到的一件事是 Vue components 不一定要无限重复使用,它们应该提供特定的功能并封装复杂的逻辑,因此您可能会更好地实现最可能的用例,如果组件不适合,您可以扩展它或为该特定事件编写一个新组件。

您可以使用 slots 来实现这一点。如果您的 <styled-input> 模板如下所示:

<div class="hasIcon">
    <slot><input></slot>
    <i class="someIcon"></i>
<div>

那么你可以这样使用它:

<styled-input>
    <input @keyup.enter="doTheThing">
</styled-input>

或者,在您不关心输入事件的情况下,如下所示:

<styled-input></styled-input>

并且将使用默认插槽内容(裸 <input>)。您可以使用 CSS 为组件内的 <input> 设置样式,但您不能向其添加自定义属性或 类,因此这种方法可能符合也可能不符合您的要求。