在看如何封装之前,先来了解一下v-model是怎么回事。
其实说白了,v-model就是change和value的结合体。
废话不多说,下面就来看一下在vue中如何封装自定义的input组件。
封装原生input:
<template>
<input type="text" :value="value" @input="handleChange" />
</template>
<script>
export default {
name: "AppInput",
model: {
prop: "value",
event: "change",
},
props: {
value: "",
},
data() {
return {
};
},
created() {},
mounted() {},
methods: {
handleChange(e) {
this.$emit("change", e.target.value);
},
},
};
</script>
封装el-input:
<template>
<!-- 对el-input进行了包装-->
<div>
<el-input
v-model="inValue"
@change="$emit('change', $event)"></el-input>
<!--
el-input提供了input事件,让我们感知el-input内部原生input值的变化,通过$event可以获取到具体的值
通过emit再次传递给父组件一个input事件,父组件中,v-on:input="searchText = $event"这句就能正常使用了
-->
<span style="color: #f56c6c; font-size: 12px;"></span>
</div>
</template>
<script>
export default {
name: "input-name",
props: {
// 保证父组件中,v-bind:value可以正常设置值
value: [String],
},
computed: {
// 获取props中value的值,并与el-input绑定,过程中不修改props中value的值,保证了单向数据流原则
inValue: {
get: function () {
return this.value;
},
set: function (newValue) {
return newValue;
},
},
}
data() {
return {
}
}
}
</script>
<style lang="less" scoped>
</style>
上面的那种方式会在回显数据时有问题。解决办法就是:如果出现异步回显数据那么就需要用计算来作为中间值转换。
<template>
<!-- 对el-input进行了包装-->
<div>
<el-input
v-model="localValue"
@change="$emit('change', $event)"></el-input>
<!--
el-input提供了input事件,让我们感知el-input内部原生input值的变化,通过$event可以获取到具体的值
通过emit再次传递给父组件一个input事件,父组件中,v-on:input="searchText = $event"这句就能正常使用了
-->
<span style="color: #f56c6c; font-size: 12px;"></span>
</div>
</template>
<script>
export default {
name: "input-name",
props: {
// 保证父组件中,v-bind:value可以正常设置值
value: [String],
},
data() {
return {
}
},
computed: {
localValue: {
get: function () {
console.log(this.value)
return this.value;
},
set: function (v) {
v;
},
},
},
}
</script>
<style lang="less" scoped>
</style>
VUE高级用法封装input
这种方式更加简洁,并且不会出现第二种封装方式出现的bug。
<template>
//这里不能使用v-model,会报子组件不能直接操作父组件传入参数的错误
//使用value的话其实就是做一个回显因为外层的v-model在这里使用已经改变了外层的值了
<el-input
v-bind="$attrs"
v-on="$listeners"
:value="value"
></el-input>
</template>
<script>
export default {
name: "f-input",
data() {
return {
};
},
props: {
value:""
},
created() {},
mounted() {},
methods: {}
};
</script>
//正常的使用方式
<f-input v-model="number"></f-input>
//不用v-model语法糖的方式
<f-input :value="number" @change="number = $event,target.value"></f-input>
这里核心用到了两个方法 attrs 和 listeners 其中 atters 可以把父组件标签上的所有自定义属性(不包括props,tyle,class)同步到当前元素中,listeners 可以把 f-input 标签上的所有方法同步到子组件中当前元素中。那么上面已经知道 v-model 就是 change 和 value 的语法糖,那在 f-input 上绑定 listeners 就可以读取到 f-input 的 v-model 的 change 事件,atters 可以读取到 f-input 的 v-model 的 value 值。
$$符号打不出来了,将就着看吧。如果还是不明白 atters和 listeners是怎么回事的可以看一下vue官网中的介绍。
看官网又出了一种封装的写法,感觉挺有意思,在这里记录一下。
默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称
子组件将需要一个 firstName prop 和 lastName prop并发出 update:firstName 和 update:lastName要同步的事件
var Component = {
props: {
firstName: String,
lastName: String
},
emits: ['update:firstName', 'update:lastName'],
//vue3可以支持多个根元素,所有这个地方不会有报错,vue2是会报错的。
template: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)">
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)">
`
}
<template>
<my-component
v-model:first-name="firstName"
v-model:last-name="lastName">
</my-component>
firstName:{{firstName}} lastName:{{lastName}}
</template>
<script>
const App = {
setup(props, context) {
const data= reactive({
firstName: 0,
lastName: 0,
});
return {
...toRefs(data),
}
},
components: {
'my-component': Component,
},
methods: { },
};
const app = createApp(App).mount('#app');
</script>
就说这么多,有什么不正确的地方欢迎小伙伴评论区讨论。