Vue 3 - V-Model 混乱

Vue 3 - V-Model Confusion

我在自学vue 3。我已经阅读了一篇又一篇关于 v-model 的文章,但每次我认为我理解了它是如何工作的,我又感到困惑。

我的目标:我构建了一个自定义下拉组件。我需要能够从 parent 控制此下拉列表的值。当下拉列表发生变化时,我想让 parent 知道新值和索引。

Child component.vue

  <div>
    <select
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
      <option v-for="option in options" :key="option">
        {{ option }}
      </option>
    </select>
  </div>
</template>

<script>
export default {
  props: ["options", "modelValue"],

  emits: ["update:modelValue"],

  methods: {
    selected() {

//??????
//want to emit this to the parent
      let selectedIndex = this.$event.target.selectedIndex + 1
      //this.$emit(value, selectedIndex)
    },
  },
};
</script>

parent.vue

<template>
  <my-drop-down :options="options" v-model="selectedOption" />
 
</template>

<script>
import myDropDown from "./components/base_dropdown.vue";

export default {
  name: "App",

  data: () => ({
    selectedOption: "2 Line",
    selectedIndex: 0,
    options: ["1 Line", "2 Line", "3 Line"],
  }),

  components: {
    myDropDown,
  },

  methods: {
    //How can I call this when the select value changes??
    onSelectChange(selected, index) {
      console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
    },

  },
};
</script>

双向绑定工作正常。我可以从 child 或 parent 控制下拉列表的值。但是如何在 child 组件

中调用 onSelectChange 方法

此外,这可能是一个愚蠢的问题,但是...

v-model="selectedOption"等同于写:value="modelValue" @input="$emit('update:modelValue', $event.target.value)"

所以parent为什么要这样写<my-drop-down :v-model="selectedOption" />

和child这样写<select :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">

而且不仅仅是 <select :v-model="selectedOption />

您看起来需要在您的父组件中使用一个观察器,它会观察 selectedOption 属性 的更改,然后使用新值从选项数组中获取索引并将其加一,并使用新值设置 selectedIndex 属性.

根据 Vuejs API section on Watchers:

Computed properties allow us to declaratively compute derived values. However, there are cases where we need to perform "side effects" in reaction to state changes - for example, mutating the DOM, or changing another piece of state based on the result of an async operation.

With Options API, we can use the watch option to trigger a function whenever a reactive property changes.

因此,对于您的代码,它可能类似于:

  watch: {
    selectedOption(newValue, oldValue) {
      console.log('In watcher. New value: ' + newValue)
      
      // get the index of the String in the array and use it to set
      // the selectedIndex:
      this.selectedIndex = this.options.findIndex(i => i === newValue) + 1;
      console.log('selectedIndex: ' + this.selectedIndex)
    }
  },

至于你问题的“第二部分”,

why is the parent written like this <my-drop-down :v-model="selectedOption" />

and the child written like this <select :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">

and not simply <select :v-model="selectedOption />

可能最好将其作为一个单独的问题提出,以保持网站要求的问题特异性,但据我所知,selectedOption 是 not 作为 select 标签的模型,事实上 selectedOption 甚至不是子组件的 属性,也不应该是。

如果你想在“select值改变时调用父组件内部的方法”,最好在 Vue watch 内部调用它,比如代码如下:

Parent component:

<template>
  <my-drop-down :options="options" v-model="selectedOption" />
</template>

<script>
import myDropDown from "../components/baseDropdown.vue";
export default {
  name: "parentModel",
  data: () => ({
    selectedOption: "2 Line",
    // selectedIndex: 0,
    options: ["1 Line", "2 Line", "3 Line"],
  }),

  components: {
    myDropDown,
  },

  computed: {
    /* It is better to use computed property for "selectedIndex", because it is related to "selectedOption" and changes accordingly. */
    selectedIndex: function () {
      return this.options.indexOf(this.selectedOption)
    }
  },

  watch: {
    selectedOption(newSelect, oldSelect) {
      this.onSelectChange(this.selectedOption, this.selectedIndex)
    }
  },

  methods: {
    //How can I call this when the select value changes??
    onSelectChange(selected, index) {
      console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
    },

  },
}
</script>

<style scoped>

</style>

Child component:

<template>
  <div>
    <select
        :value="modelValue"
        @change="$emit('update:modelValue', $event.target.value)"
    >
      <!-- You can use v-model also here. But it only changes the value of "modelValue" and does not emit anything to parent component. -->
<!--    <select v-model="modelValue">-->
      <option v-for="option in options" :key="option">
        {{ option }}
      </option>
    </select>
  </div>
</template>

<script>
export default {
  name: "baseDropdown",
  props: ["options", "modelValue"],

  emits: ["update:modelValue"],

  /* --------------------------------- */
  /* You don't need this method, because "$emit('update:modelValue', $event.target.value)" that is used in "select" tag itself is enough to emit data to the parent component. */
  /* --------------------------------- */

//   methods: {
//     selected() {
//
// //??????
// //want to emit this to the parent
//       let selectedIndex = this.$event.target.selectedIndex + 1
//       //this.$emit(value, selectedIndex)
//     },
//   },
}
</script>

<style scoped>

</style>

关于问题的第二部分:

v-model="selectedOption" is the same as writing :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"

我认为这不是真实的陈述,原因有二:

  1. 原因一:根据Vue docs

v-model="selectedOption" is the same as writing :value="selectedOption" @input="event => selectedOption = event.target.value"

你在上面的语句中看不到任何$emit。但是在您的情况下,您想将数据发送到父组件。

  1. 原因二:再次根据Vue docs<select>标签最好使用change as an event