Input组件封装的v-model问题

一、关于组件的封装:

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的做法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值