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)"
我认为这不是真实的陈述,原因有二:
- 原因一:根据Vue docs:
v-model="selectedOption" is the same as writing :value="selectedOption"
@input="event => selectedOption = event.target.value"
你在上面的语句中看不到任何$emit
。但是在您的情况下,您想将数据发送到父组件。
- 原因二:再次根据Vue docs,
<select>
标签最好使用change as an event
。
我在自学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)"
我认为这不是真实的陈述,原因有二:
- 原因一:根据Vue docs:
v-model="selectedOption" is the same as writing :value="selectedOption" @input="event => selectedOption = event.target.value"
你在上面的语句中看不到任何$emit
。但是在您的情况下,您想将数据发送到父组件。
- 原因二:再次根据Vue docs,
<select>
标签最好使用change as an event
。