一、关于组件的封装:
v-bind="$attrs"与v-on="$listener"是组件封装或二次封装过程中经常使用的指令,前者v-bind用于监听父组件中不被认为prop的属性传入到子组件中,后者v-on用于指定父组件的事件(非子组件emit的事件)绑定在子组件中。举个最简单的例子,例如我们封装一个swiper组件:
<template>
<div class="container">
<swiper
v-bind="$attrs"
v-on="$listener"
>
<slot></slot>
<swiper>
</div>
</template>
那么我们在父组件中使用<Myswiper></Myswiper>组件时就可以在Myswiper组件上直接绑定swiper具有的一切属性和事件
二、关于v-model的本质
以上方式对于大多数组件封装可能都是没有问题的,但对于Input组件可能就略显不同了,因为对vue大多数开发者绑定Input的值可能更倾向于v-model而不是value本身。
v-model本质是 :value 与 @input的缩写(以下两行代码是等价的)
<input v-model="data"/>
<input :value="data" @input="data=e.detail.value"/>
三、Input组件二次封装使用v-model带来的问题
直接上封装的代码:
<!--子组件-->
<template>
<view class="container">
<slot name="prefix"></slot>
<input
v-bind="$attrs"
v-on="$listeners"
:value="v"
@input="this.$emit('input',e.detail.value)"
/>
<slot name="suffix"></slot>
</view>
</template>
<!--父组件-->
<Myinput
v-model="data"
placeholder="请输入搜索内容"
@focus="handleFocus"
></Myinput>
以上封装是为了对input组件做一个前后的icon图标,乍一看好像没什么问题,但实际运行起来在不同平台会有不同的意想不到的表现(有兴趣的小伙伴可以试试,这里不做统一总结)。总之你会发现,怎么v-model不生效了?于是乎,就会有网上千篇一律复制粘贴的解决方案,Myinput中用:value和@input代替v-model或者Myinput中监听子组件传来的值以修改data的值等等。
那么请问,为什么elementui中封装的el-input组件可以直接使用v-model呢??????
四、解决:
我们来看一下el-input组件源码
<input
:tabindex="tabindex"
v-if="type !== 'textarea'"
class="el-input__inner"
v-bind="$attrs"
:type="showPassword ? (passwordVisible ? 'text': 'password') : type"
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete || autocomplete"
ref="input"
@compositionstart="handleCompositionStart"
@compositionupdate="handleCompositionUpdate"
@compositionend="handleCompositionEnd"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
>
handleInput(event) {
/*...some code in here*/
this.$emit('input', event.target.value);
/*...some code in here*/
},
handleChange(event) {
this.$emit('change', event.target.value);
},
仔细观察的小伙伴会发现,el-input组件的封装并未使用v-on="$listener",反之是它干脆把常用的input事件都给手动监听一下然后暴露给外部组件使用。为什么?
因为如果在子组件使用v-bind和v-on监听,并在外部组件中使用v-model绑定,因为代码执行器在执行v-model时相当于执行了:value和@input,你可以手动把v-model换成:value和@input,如下
<!--子组件-->
<template>
<view class="container">
<slot name="prefix"></slot>
<input
v-bind="$attrs"
v-on="$listeners"
:value="v"
@input="this.$emit('input',e.detail.value)"
/>
<slot name="suffix"></slot>
</view>
</template>
<!--父组件-->
<Myinput
:value="data"
placeholder="请输入搜索内容"
@input="consInput"
></Myinput>
consInput (e) {
console.log('run',e);
this.data = e;
}
你会发现,在输入框输入一次,consInput事件会执行两次。其中原因是会先执行子组件的@input,再执行父组件的@input,子组件的input执行原因是父组件通过子组件的v-on="$listener"绑定上去的,父组件的执行原因是子组件emit过来的input事件执行。
总结:对于input这类需要绑定值的组件封装,如果想在外层组件使用v-model,那就不建议封装时使用v-on="$listener",如果你确实需要在外层组件使用input的一些事件,那就emit手动暴露事件,具体可参见el-input的做法。