vue的v-model

188 阅读1分钟

v-model的问题

我们都知道v-model是这样实现父子组件的交互的:

<BaseInput :label="'姓名'" v-model="username" />
<!--
相当于 :
<BaseInput :label="'姓名'" :value="username" @input="fn" />
fn具有一些逻辑 , 大体上是传递出一个当前input的value值然后赋给username 我猜的
-->
<script>
	watch:{
        username(val){//监听username的值
          console.log("姓名" , val);
        }
    },
</script>

再看子组件的实现:

<template>
    <div>
        <label for="username" class="label">
            {{label}}
        </label>
        <input id="uesrname" type="text" v-on:input="inputHandle" :value="recValue">
    </div>
</template>

<script>
export default {
    props:["value" , "label"],//接受value 与 label
    data(){
        return{
            recValue:this.value//为value赋值
        }
    },
    methods:{
        inputHandle(e){//当这个input元素发生输入事件后
            this.$emit("input" , e.target.value);//触发父组件上的input事件 , 外面父组件的监听器一直在打印值 , 说明我们在这里只要触发
            //这个事件并给这个事件传入输入的值作为参数 , 那么外部隐藏的input事件自然会把外部v-model绑定的变量改变
        }
    },
    computed:{
        
    }
}
</script>

<style>
.label{
    width: 30px;
    letter-spacing: 5px;
}
#uesrname{
    width: 150px;
    height: 30px;
    border: 1px solid #666;
    border-radius: 3px;
    vertical-align: middle;
    outline: none;
    /* outline-color: aquamarine; */
}

#uesrname:focus{
    border-width: 3px; ;
    border-color: aquamarine;
}
</style>

后来我又想了想 , 要不咱们自定义外部的input事件 , 看看会咋样呗:

<BaseInput :label="'姓名'" v-on:input="inputHandle" v-model="username"/>
inputHandle(val){
    console.log("传出的值" , val)
    this.username = val;
},
<!--自定义的input事件做的事情也简单 , 就是简单的把传出的值赋值给username变量-->

会被触发

到此为止 先理一下思绪 :

父组件中 , v-model绑定的username变量的值先被动态传入组件(:value="username") , 并且在子组件中被props的value属性接收

然后 , 子组件中的input框每次输入都会触发 $emit触发外部的input函数 , 并且将此时的input框中的值传出去

最后在外部触发自定义的input事件完成值赋给username变量

到此为止 , 就是所有的过程了

将外部绑定的多个事件批量绑定到内部的input元素上:

上面我们知道 , 其实主要的核心就是组件内部触发组件外部的事件 , 并传出想传的参数

但是这不能让我们**'将这个组件当做真正的input'**来使用 , 为此我们需要借助Vue提供的$listeners,这个组件属性可以帮我们获取所有在父组件中使用本组件时绑定的所有事件(区别于.native修饰符只能绑定在子组件的根元素上) , 利用这个属性可以将指定的事件绑定在指定的元素上.

<!--$listeners属性指向一个对象 , 这个对象如下:
{
	input : function(){},
	focus : function(){}
	aaa : function(){} 

} 可以把这些事件都看做是自定义事件-->

<!--v-on=上面的对象
	相当于 
	v-on:input = "function(){}"
	v-on:focus = "function(){}"
	v-on:aaa = "function(){}"
-->
<!--父组件-->
<BaseInput :label="'姓名'" v-on:input="inputHandle" v-model="username"  v-on:focus="focusHandle" v-on:blur="blurHandle"/>
<script>
    data(){
        return {
            username:""
        }
    }
	methods:{
        inputHandle(val){
            console.log("传出的值" , val)
            this.username = val;
        },
        focusHandle(){
            console.log("获取焦点了")
        },
        blurHandle(){
            console.log("失去焦点了");
        }
    }
</script>

<!--子组件-->
<template>
    <div>
        <label for="username" class="label">
            {{label}}
        </label>
        <input id="uesrname" type="text" v-on="inputListeners" v-bind:value="recValue">
    </div>
</template>

<script>
export default {
    props:["value" , "label"],
    data(){
        return{
            recValue:this.value
        }
    },
    computed:{
        inputListeners(){
            var vm = this;
            return Object.assign({},
                //从父级添加所有监听器
                this.$listeners , 
                {
                    //可以覆写一些方法
                    input(e){
                        vm.$emit("input",e.target.value);
                    }
                }
            )
        }
    }
}
</script>

<!--起初是这样改的 , 但是这样是有问题的 
	我们知道 v-bind 绑定的属性只能从 vm ==> View 
	因此一开始输入 字符 a 
	触发input函数 , 进而触发外部的input , 外部自然就会获取到传出的字符 a
	但是 , 立马 , 由于recValue此时仍是"" , 所以 此时输入框仍是 显示空白
	理论上输入多少次都是空白 , 可能是Vue的缓存原因有时会在input框中存在字符
	可以改成下面这样 使用v-model绑定 , 其他不变
	
-->
<input id="uesrname" type="text" v-on="inputListeners" v-model:value="recValue">
<!--
先不管此时的v-model隐藏的'v-on:input'与我们自己定义的有何区别 , 首先需要知道的是 , v-model是双向绑定的 , 不管其内部的实现细节 , 只需要知道 , 输入框的内容改变 , 与输入框绑定的recValue就会改变
-->

但是这仍不能让我们'正常使用' BaseInput , 再次修改:

<!--子组件-->
<template>
    <div>
        <label for="username" class="label">
            {{label}}
        </label>
        <input id="uesrname" type="text" v-on="inputListeners" v-bind:value="recValue">
    </div>
</template>
<script>
export default {
    props:["value" , "label"],
    data(){
        return{
            // recValue:this.value
        }
    },
    computed:{
        recValue(){//会随着传入的值的变化而改变 , 进而改变input框当前的内容
            return this.value;
        },
        inputListeners(){
            // var vm = this;
            return Object.assign({},
                //从父级添加所有监听器
                this.$listeners , 
                // {
                //     //可以覆写一些方法
                //     input(e){
                //         vm.$emit("input",e.target.value);
                //     }
                // }
            )
        }
    }
}
</script>
<!--父组件-->
<BaseInput :label="'姓名'" v-on:input="inputHandle" v-model="username"  v-on:focus="focusHandle" v-on:blur="blurHandle"/>
    inputHandle(e){
      console.log("传出的值" , e)//这里需要传入事件参数
      this.username = e.target.value;
    },
    focusHandle(){
      console.log("获取焦点了")
    },
    blurHandle(){
      console.log("失去焦点了");
    }

这样 BaseInput就可以当做正常的input来使用了