1、组件结构
(1)父组件
<template>
<div>
<SearchBar v-model="searchData" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import SearchBar from './components/SearchBar.vue'
const searchData = ref({
keyword:'',
placeholder:'请输入你要查询的关键字',
options:[{
{ label:'视频',value:'video'},
{ label:'文章', value:'article'},
{ label:'用户', value:'user'},
}],
selectedValue:'video',
})
</script>
(2)子组件 SearchBar.vue
<template>
<el-input v-model="modelValue.keyword"
:placeholder="modelValue.placeholder"
>
<template #prepend>
<el-select
v-model="modelValue.selectedValue"
placeholder="Select"
style="width:85px"
>
<el-option
v-for="item in modelValue.options"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</template>
<template #append>
<el-button :icon="Search">
</template>
</el-input>
</template>
<script setup>
import { Search } from '@element-plus/icon'
const props=defineProps({
modelValue:{
type:Object,
required:true
}
})
</script>
直接把父组件传递过来的数据绑定进去了,这会有什么问题呢?
现在直接变成了这种结构:
打破了单项数据流,每次打破单项数据流,你的工程就离屎山更近一步 ~~
方案一:
最笨的办法呢,在子组件里面就不能用v-model了,不然的话文本框一变,父组件的数据不就跟着改了吗,所以得把它拆开,拆成两个,一个是:modelValue,另一个是@update:modelValue,当事件触发的时候我调用这个函数
handlekeywordChange 函数要做的事情就是去触发子组件的emit:update:modelValue
需要在子组件里定义事件(子组件变化之后触发这个事件):
子组件 SearchBar.vue
<template>
<el-input
:modelValue = "modelValue.keyword"
@update:modelValue = "handleKeywordChange"
:placeholder="modelValue.placeholder"
>
<template #prepend>
<el-select
v-model="modelValue.selectedValue"
placeholder="Select"
style="width:85px"
>
<el-option
v-for="item in modelValue.options"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</template>
<template #append>
<el-button :icon="Search">
</template>
</el-input>
</template>
<script setup>
import { Search } from '@element-plus/icon'
const props=defineProps({
modelValue:{
type:Object,
required:true
}
})
const emit = defineEmits(['update:modelValue'])
function handleKeywordChange(val){
// val只是modelValue里面其中一个属性,但是要传的是modelValue对象,所以做个简单处理啦~~
emit('update:modelValue',{
...props.modelValue,
keyword:val
})
}
</script>
虽然麻烦 但是保证了单项数据流。
方案二:
使用计算属性
在 子组件 SearchBar.vue 中:
在el-input里面依然改成v-model:
但是呢,这个简化并不多,每一项v-model都需要做一个计算属性。继续简写 直接返回这个对象:
绑定计算属性的具体字段:
一个计算属性就解决所有问题,但是真的可以吗???
我改变的是model里面keyword属性,计算属性set是收不到通知的,无法触发。
在这里返回一个代理对象
cnost model = computed({
get(){
return new Proxy(props.modelValue,{
// 代理的属性,属性名和属性值
set(obj,name,val){
// 你想改变对象里面的某个属性,就意味着你是改动了整个对象,要给他生成一个新的对象
emit('update:modelValue',{
...obj,
[name]:val
});
return true
}
})
}
})
每次这样写很麻烦,把它提出去,写成一个辅助函数:
// 属性对象,属性名,emit函数
export function useVModel(props,propName,emit){
return computed({
get(){
return new Proxy(props[propName],{
set(obj,name,val){
emit('update:'+ propName,{
...obj,
[name]:val
});
return true
}
})
},
set(val){
emit('update:' + propName,val)
}
})
}
子组件页面调用:
既保留了单项数据流,又简化了代码~~