美文网首页
组件封装必学之实现v-model语法糖

组件封装必学之实现v-model语法糖

作者: 辉夜真是太可爱啦 | 来源:发表于2021-08-02 13:38 被阅读0次

    本文将讲述如何在自定义的公用组件上实现 v-model,在实际项目的公共组件开发中有着很大的帮助!

    学习目的

    在自己封装组件的时候,特别是输入框,下拉选择框等交互组件的时候,一般绑定值的时候,采用的是 v-model,使用 v-model 的主要好处是无需记特定的 prop 字段名,即可绑定到组件中的值,降低组件的使用成本。

    毕竟,一个好的公共组件,首先是 API 的设计应该让人容易理解,并且使用方便。

    其次,应该尽量将重复的逻辑处理放在子组件中,这样子才会让组件的封装更有意义。

    当然,通过本文的学习,即使不是交互组件,任何组件都可以通过这种方式来实现 v-model

    下面就让我们一起来学习如何在公用组件上进行封装 v-model

    v-model基本概念

    v-model 实际上就是 $emit('input') 以及 props:value 的组合语法糖,只要组件中满足这两个条件,就可以在组件中使用 v-model

    虽然在有些交互组件中有些许不同,例如:

    checkboxradio 使用 props:checked 属性和 $emit('change') 事件。

    select 使用 props:value 属性和 $emit('change') 事件。

    但是,除了上面列举的这些,别的都是 $emit('input') 以及 props:value

    实现数字计步器

    既然已经知道了 v-model 的具体实现原理,那么,我们现在就来尝试自己封装一个数字计步器,主要由两个增减按钮,以及一个输入框组成。

    新建组件

    先新建一个 NumberInput 组件

    <template>
      <div>
        <button>-1</button>
        <input type="text" />
        <button>+1</button>
      </div>
    </template>
    
    <script>
      export default {
        name: "NumberInput"
      }
    </script>
    

    props:value

    先写上第一个条件, props:value ,由于是数字计步器,所以 value 的类型必定是数值型,并且,肯定是一个必定传递的参数。

    <input type="number" :value="value" />
    
    props:{
      value:{
        type: Number,
        default: 0,
        require: true
      }
    }
    

    当然,写到这里,就会有一个问题产生,由于 props 有一个特性,那就是单向数据流,对于但向数据流的理解。

    你也可以称之为单向下行绑定,相当于就是父组件的值通过 props 传递给子组件,父组件中值的修改会传递到子组件,而子组件中对于这个值的修改则不能传递给父组件。(虽然可以,控制台会给出警告,而且官方也不推荐这样子做)

    你可以想象成一个自上而下的瀑布,父组件在上,子组件在下,这个水流只能自上而下,而不能自下而上,那就不对劲了,牛顿大哥也不答应啊。

    当然,这个单向数据流的出现,主要是为了防止从子组件意外更新父组件的状态,从而导致你的应用的数据流向难以理解。

    $emit('input')

    上面提及到的单向数据流中的有一个要点,那就是你不应该在一个子组件内部改变 props

    所以,我们需要换一种方式来改写,通过定义一个变量 currentValue ,来避免对于子组件中 props 的直接修改,所有对于 <input> 输入框的修改,都通过这个 currentValue 来记录。

    <input type="number" :value="currentValue" />
    
    props:{
      value:{
        type: Number,
        default: 0
        require: true
      }
    },
    data(){
      return{
        currentValue: this.value
      }
    }
    

    而此时,我们先修改 currentValue 的值,然后通过 $emit('input') 来通知父组件,我们的 value 的值发生改变了,使父组件的 props 值进行修改,再通过父组件的单向数据流,让子组件中的值更新。从而避免对于子组件中 props 的直接修改。

    <input type="number" :value="currentValue" @input="changeValue" />
    
    methods:{
      changeValue(e){
        this.currentValue = parseInt(e.target.value);
        this.$emit('input', this.currentValue);
      }
    }
    

    现在,就完美地避开了单向数据流所带来的问题,通过一个 current 变量来记录值,并且避免对于 props 的直接修改。然后通过 $emit('input') 让父组件中绑定的值进行修改,通过 单向下行绑定 ,由父组件修改 props

    那么,同理,左右两边的累加器也就很简单了,也是对 currentValue 值的修改,再加上 $emit('input') 的传递。

    <button @click="increase(-1)">-1</button>
    <button @click="increase(1)">+1</button>
    
    methods:{
      increase(value){
        this.currentValue+= value;
        this.$emit('input', this.currentValue);
      }
    }
    

    至此,还有剩下一个很关键的步骤,那就是 watch 监听

    watch 监听

    当组件初始化时从 value 获取一次值,并且当父组件直接修改 v-model 绑定值的时候,对于 value 的及时监听就显得尤为重要。

    所以,最后,我们还要加一步 watch 监听。

    watch:{
      value(newVal){
        this.currentValue = newVal;
      }
    },
    

    至此,一个 v-model 的组件就封装好啦。

    所以,完整的代码如下

    <template>
      <div>
        <button @click="increase(-1)">-1</button>
        <input type="number" :value="currentValue" @input="changeValue" />
        <button @click="increase(1)">+1</button>
      </div>
    </template>
    
    <script>
      export default {
        name: "NumberInput",
        props:{
          value:{
            type: Number,
            default: 0,
            require: true
          }
        },
        data(){
          return{
            currentValue: this.value
          }
        },
        watch: {
          value(newVal){
            this.currentValue = newVal;
          }
        },
        methods:{
          changeValue(e){
            this.currentValue = parseInt(e.target.value);
            this.$emit('input', this.currentValue);
          },
          increase(value){
            this.currentValue+= value;
            this.$emit('input', this.currentValue);
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
    
    </style>
    

    组件使用

    <NumberInput v-model="number"></NumberInput>
    
    import NumberInput from "./NumberInput";
    
    export default {
      components:{NumberInput},
      data(){
        return{
          number: 10
        }
      }
    }
    

    文末总结

    无论是任何组件,都可以实现 v-model

    而实现 v-model 的要点,主要就是以下几点:

    • props:value

      用来控制 v-model 所绑定的值。

    • currentValue

      由于 单向数据流 的原因,需要使用 currentValue 避免子组件对于 props 的直接操作。

    • $emit('input')

      用来控制 v-model 值的修改操作,所有对于 props 值的修改,都要通知父组件。

    • watch 监听

      当组件初始化时从 value 获取一次值,并且当父组件直接修改 v-model 绑定值的时候,对于 value 的及时监听。

    文末彩蛋 model

    比方有些人说我就是不想用 props:value 以及 $emit('input') ,我想换一个名字,那么此时, model 可以帮你实现。

    因为这两个名字在一些原生表单元素里,有其它用处。

    export default {
      model: {
        prop: 'number',
        event: 'change'
      },
    }
    

    这种情况下,那就是使用 props:number 以及 $emit('change')

    相关文章

      网友评论

          本文标题:组件封装必学之实现v-model语法糖

          本文链接:https://www.haomeiwen.com/subject/bgzdvltx.html