提取自定义组件时如何让el-select和v-model协同工作

how to make el-select and v-model work together when extracting a custom component

我正在使用 el-select 构建一个 select 组件。像这样:

<template>
    //omitted code
    <el-select v-model="filterForm.client"
                    filterable
                    remote
                    placeholder="Please enter a keyword"
                    :remote-method="filterClients"
                    :loading="loading">
                <el-option
                        v-for="item in clientCandidates"
                        :key="item._id"
                        :label="item.name"
                        :value="item._id">
                </el-option>
            </el-select>
</template>
<scripts>
    export default {
        data() {
           filterForm: {
                client: ''
           },
           clientCandidates: [],
           loading: false
        },
        methods: {
            filterClients(query) {
                if (query !== '') {
                    this.loading = true;
                    setTimeout(() => {
                        this.loading = false;
                        this.clientCandidates = [{_id: '1', name: 'foo'}, {_id: '2', name: 'bar'}];
                    }, 200);
                } else {
                    this.clientCandidates = [];
                }
            }
        }
    }
</scripts>

到目前为止一切顺利,但是由于组件会出现在不同的页面中,所以我想提取一个自定义组件以避免重复。

根据 guideline

v-model="fullName"

等同于

v-bind:value="fullName"
v-on:input="$emit('input', $event)"

所以我提取了 select 组件,如下所示:

<template>
<el-select
        v-bind:value="clientId"
        v-on:input="$emit('input', $event)"
        placeholder="Filter by short name"
        filterable="true"
        remote="true"
        :remote-method="filter"
        :loading="loading">
    <el-option
            v-for="item in clients"
            :key="item._id"
            :label="item.name"
            :value="item._id">
    </el-option>
</el-select>
</template>
<scripts>
export default {
    props: {
        clientId: {
            type: String,
            required: true
        }
    },
    data() {
        return {
            clients: [],
            loading: false,
        }
    },
    methods: {
        filter(query) {
            if (query !== '') {
                this.loading = true;
                setTimeout(() => {
                    this.loading = false;
                    this.clients = [{_id: '1', name: 'foo'}, {_id: '2', name: 'bar'}];
                }, 200);
            } else {
                this.clients = [];
            }
        }
    }
}
</scripts>

父组件如下所示:

<select-client v-model="filterForm.clientId"></select-client>

select 下拉列表工作正常,但不幸的是,select 没有显示我 select 编辑的选项,在我选择一个选项后它仍然是空的。我怀疑也许我应该将 v-on:input 切换为 'v-on:change',但它也不起作用。

更新 我创建了一个简单的示例,您可以克隆它 here,请查看 el-select-as-component 分支。 运行

npm install
npm run dev

您将看到一个简单的页面,其中包含 3 种 select:
左边的是一个用raw select 编写的自定义组件,它工作正常。
中间一个是用el-select编写的自定义组件,下拉列表仍然是空的,但是单击Filter按钮后,您可以在控制台中看到filterForm.elClientId。这就是我提出这个问题的原因。
右边的是普通的el-select,很好用

指南说 v-model 等同于 v-bind:valuev-on:input 但如果你仔细观察,在侦听器函数中,绑定的变量是用事件设置的 属性.你在你的例子中所做的是不一样的,在你的听众中你发出另一个事件。除非你抓住这个新事件,否则你的价值将永远不会被设置。

另一件事是你不能修改道具,你应该把它当作一个 read-only 变量。

如果你想从 parent 监听到 child 组件发出的事件,你必须做这样的事情

<template>
  <el-select
    :value="selected"
    @input="dispatch"
    placeholder="Filter by short name"
    :filterable="true"
    :remote="true"
    :remote-method="filter"
    :loading="loading">
    <el-option
      v-for="item in clients"
      :key="item._id"
      :label="item.name"
      :value="item._id">
    </el-option>
  </el-select>
</template>

<script>
export default {
  name: 'SelectClient',

  data() {
    return {
      selected: '',
      clients: [],
      loading: false,
    }
  },

  methods: {
    filter(query) {
      if (query !== '') {
        this.loading = true;
        setTimeout(() => {
          this.loading = false
          this.clients = [{_id: '1', name: 'foo'}, {_id: '2', name: 'bar'}]
        }, 200)
      } else {
        this.clients = []
      }
    },

    dispatch (e) {
      this.$emit('input', e)
      this.selected = e
    }
  }
}
</script>

注意:v-model + watch 模式也可以。重要的是$emit输入事件,所以parent中的v-model会被更新。

并且在您的 parent 中,您可以像这样使用此组件:<select-client v-model="clientId"/>

提示:如果你想在不同的地方修改相同的数据,你应该有一个单一的真实来源并且更喜欢vuex。那么你的组件将是这样的

<template lang="html">
  <select
    v-model="clientId">
    <option
      disabled
      value="">Please select one</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
</template>

<script>
export default {
  data () {
    return {
      clientId: ''
    }
  },

  watch: {
    clientId (newValue) {
      // Do something else here if you want then commit it
      // Of course, listen for the 'setClientId' mutation in your store
      this.$store.commit('setClientId', newValue)
    }
  }
}
</script>

然后在你的其他组件中,你可以监听$store.state.clientId值。