【问题标题】:vue 3 - binding problem with select, if option selected is not showing in the new values, select is in blank,but it doesn't change to ""vue 3 - 选择绑定问题,如果选择的选项未显示在新值中,则选择为空白,但不会更改为“”
【发布时间】:2021-11-15 15:45:12
【问题描述】:

我有两个组件,一个输入和一个选择,选择显示选项取决于您在输入中写入的内容,但如果所选选项未显示在新选项中,请选择默认选项或第一个选项“选择选项”

当我选择一个选项然后更改 Select 中的选项并且所选选项不在新选项中时,Select 显示为空白,但是当我将所选选项更改为“”时,Select 不会更改到它(选择具有值“”的选项),我不知道为什么,但是如果我更改为其他选项,则选择会更改为它...

示例:
选择选项 2 > 在输入中写入 3 个字符

您可以看到变量option_selected 与选择更改为“”绑定,但选择不会更改为“选择选项”

此示例的文档链接:
Component custom events

const input_text = {
  name: "input-min-length",
  props: {
    text: String,
    is_min_length: Boolean 
  },
  emits: ["update:text", "update:is_min_length"],
  computed: {
    is_min(){
      return this.text.length >= 3;
    }
  },
  watch: {
     is_min(new_value){
      this.$emit("update:is_min_length", this.is_min);
     }
  },
  template: `<div><input type="text" :value="text" @input="$emit('update:text', $event.target.value)" /></div>`
};

const select_options = {
  name: "select-options",
  props: {
    option_selected: String,
    is_min_length: Boolean
  },
  emits: ["update:option_selected"],
  data() {
    return {
      options: [
        {text: "Select option", value: ""},
        {text: "option 1", value: "1", is_min_length: true},
        {text: "option 2", value: "2"},
        {text: "option 3", value: "3", is_min_length: true},
      ]
    };
  },
  computed: {
    options_filtered(){
      if(this.is_min_length == false) return this.options;
      return this.options.filter((option) => option.is_min_length == true || option.value == "")
    }
  },
  watch: {
    is_min_length(){
      const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
      if (is_presented == false){
        setTimeout(() => {
          this.$emit("update:option_selected", "");
        }, 0); // I try to use setTimeout, to see if it changes at all
      }
    }
  },
  template: `
    <select :value="option_selected" @change="$emit('update:option_selected', $event.target.value)">
      <option v-for="option in options_filtered" :value="option.value" :key="option.value">
        {{ option.text }}
      </option>
    </select>
  ` 
}


const app = {
  components:{
    "input-min-length": input_text,
    "select-options": select_options
  },
  data() {
    return {
      text: "",
      is_min_length: false,
      option_selected: ""
    }
  },
  template: `
  <div>
    <input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
    <select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
    <button @click="option_selected='1'">change to opt 1</button><br>
    <button @click="option_selected=''">change to opt ""</button><br>
    <div>
      <strong>DATA:</strong><br>
      <strong>text:</strong> "{{text}}"<br>
      <strong>is_min_length:</strong> {{is_min_length}}<br>
      <strong>option_selected:</strong> "{{option_selected}}"
    </div>
  </div>`

}


Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue@next"></script>
<div id="app"></div>

【问题讨论】:

    标签: vue.js select input binding vue-component


    【解决方案1】:

    哇,这是一个很难深入研究的问题。

    什么是“价值”

    所以&lt;select&gt; 元素没有HTML value 属性,但是HTMLSelectElement 上的DOM API 提供了一个el.value 属性,它可以为您提供所选选项的值。 Vue 提供了对这个value 属性的绑定(通过v-model 使用)。这就是为什么我们可以在&lt;select&gt; 元素中简单地使用:value

    您的代码有问题:

    选择“选项2”时,然后通过optionsFiltered从DOM删除,将&lt;select&gt;元素设置为无效状态。在无效状态下,元素的 el.value 返回 ''(一个空字符串)(注意:这是 DOM API,不是 Vue)。现在is_min_length 上的观察者被触发并发出一个update:option_selected 事件,其值为''(一个空字符串)。如您所知,Vue 是反应式的。由于el.value 已经是'',我想Vue 认为不需要更新DOM,因此从不调用el.value = ''。 (将 el.value 设置为空字符串,即使它已经是一个空字符串确实可以提供所需的行为,DOM API 似乎非常健壮,它的 Vue 没有调用它)。

    解决方案

    • 最简单的方法是将默认的“选择选项”值设置为'' 以外的值,这样它就不会与无效状态的'' 发生冲突。例如您可以将其设置为0。或者可能是一个字符串'none'

    const input_text = {
      name: "input-min-length",
      props: {
        text: String,
        is_min_length: Boolean 
      },
      emits: ["update:text", "update:is_min_length"],
      computed: {
        is_min(){
          return this.text.length >= 3;
        }
      },
      watch: {
         is_min(new_value){
          this.$emit("update:is_min_length", this.is_min);
         }
      },
      template: `<div><input type="text" :value="text" @input="$emit('update:text', $event.target.value)" /></div>`
    };
    
    const select_options = {
      name: "select-options",
      props: {
        option_selected: String,
        is_min_length: Boolean
      },
      emits: ["update:option_selected"],
      data() {
        return {
          options: [
            {text: "Select option", value: "0"},
            {text: "option 1", value: "1", is_min_length: true},
            {text: "option 2", value: "2"},
            {text: "option 3", value: "3", is_min_length: true},
          ]
        };
      },
      computed: {
        options_filtered(){
          if(this.is_min_length == false) return this.options;
          return this.options.filter((option) => option.is_min_length == true || option.value == "0")
        }
      },
      watch: {
        is_min_length(){
          const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
          if (is_presented == false){
            setTimeout(() => {
              this.$emit("update:option_selected", '0');
            }, 0); // I try to use setTimeout, to see if it changes at all
          }
        }
      },
      template: `
        <select :value="option_selected" @change="$emit('update:option_selected', $event.target.value)">
          <option v-for="option in options_filtered" :value="option.value" :key="option.value">
            {{ option.text }}
          </option>
        </select>
      ` 
    }
    
    
    const app = {
      components:{
        "input-min-length": input_text,
        "select-options": select_options
      },
      data() {
        return {
          text: "",
          is_min_length: false,
          option_selected: "0"
        }
      },
      template: `
      <div>
        <input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
        <select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
        <button @click="option_selected='1'">change to opt 1</button><br>
        <button @click="option_selected='0'">change to opt ""</button><br>
        <div>
          <strong>DATA:</strong><br>
          <strong>text:</strong> "{{text}}"<br>
          <strong>is_min_length:</strong> {{is_min_length}}<br>
          <strong>option_selected:</strong> "{{option_selected}}"
        </div>
      </div>`
    
    }
    
    
    Vue.createApp(app)
    .mount('#app')
    <script src="https://unpkg.com/vue@next"></script>
    <div id="app"></div>
    • 另一种解决方案是在发出事件的同时自己调用el.value = '',但我不建议这样做,因为它会使其他 Vue 开发人员更难理解您的代码(即使您是只有一个人参与该项目):

    const input_text = {
      name: "input-min-length",
      props: {
        text: String,
        is_min_length: Boolean 
      },
      emits: ["update:text", "update:is_min_length"],
      computed: {
        is_min(){
          return this.text.length >= 3;
        }
      },
      watch: {
         is_min(new_value){
          this.$emit("update:is_min_length", this.is_min);
         }
      },
      template: `<div><input type="text" :value="text" @input="$emit('update:text', $event.target.value)" /></div>`
    };
    
    const select_options = {
      name: "select-options",
      props: {
        option_selected: String,
        is_min_length: Boolean
      },
      emits: ["update:option_selected"],
      data() {
        return {
          options: [
            {text: "Select option", value: ""},
            {text: "option 1", value: "1", is_min_length: true},
            {text: "option 2", value: "2"},
            {text: "option 3", value: "3", is_min_length: true},
          ]
        };
      },
      computed: {
        options_filtered(){
          if(this.is_min_length == false) return this.options;
          return this.options.filter((option) => option.is_min_length == true || option.value == "")
        }
      },
      watch: {
        is_min_length(){
          const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
          if (is_presented == false){
            setTimeout(() => {
              this.$emit("update:option_selected", "");
              document.getElementById("myselect").value = "";
            }, 0); // I try to use setTimeout, to see if it changes at all
          }
        }
      },
      template: `
        <select id="myselect" :value="option_selected" @change="$emit('update:option_selected', $event.target.value)">
          <option v-for="option in options_filtered" :value="option.value" :key="option.value">
            {{ option.text }}
          </option>
        </select>
      ` 
    }
    
    
    const app = {
      components:{
        "input-min-length": input_text,
        "select-options": select_options
      },
      data() {
        return {
          text: "",
          is_min_length: false,
          option_selected: ""
        }
      },
      template: `
      <div>
        <input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
        <select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
        <button @click="option_selected='1'">change to opt 1</button><br>
        <button @click="option_selected=''">change to opt ""</button><br>
        <div>
          <strong>DATA:</strong><br>
          <strong>text:</strong> "{{text}}"<br>
          <strong>is_min_length:</strong> {{is_min_length}}<br>
          <strong>option_selected:</strong> "{{option_selected}}"
        </div>
      </div>`
    
    }
    
    
    Vue.createApp(app)
    .mount('#app')
    <script src="https://unpkg.com/vue@next"></script>
    <div id="app"></div>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-18
      • 2012-10-19
      • 1970-01-01
      • 1970-01-01
      • 2015-07-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多