zoukankan      html  css  js  c++  java
  • [Vue深入组件]:v-model语法糖与自定义v-model

    1. v-model 语法糖

    当你希望一个自定义组件的值能够实现双向绑定。 那么就需要:

    1. 将值传入组件;
    2. 将变化的值逆传回父组件。

    实际上,就可以利用 props 实现的父传子 + 通过自定义事件this.$emit实现的子传父。实现双向的数据流传递。

    下面是一个示例:

    有这样一个父组件:

    <template>
      <div>
        <Child :cusProp="message" @cusEvent="message = $event" />
        文字:{{message}}
      </div>
    
    </template>
    <script>
    import Child from "./comps/child.vue"
    export default {
      components: {
        Child
      },
      data() {
        return {
          message: 'init default'
        }
      }
    }
    </script>
    

    和这样的一个子组件:

    <template>
      <div>
        this is child comp
        <input type="text" :value="cusProp" @input="onInputChange">
      </div>
    </template>
    <script>
    export default {
      props:["cusProp"],
      methods: {
        onInputChange(e) {
          this.$emit('cusEvent', e.target.value)
        }
      }
    }
    </script>
    

    image-20210824220325859

    我们自定义了一个组件,名为<Child /> , 我们通过 v-bind:cusProp<Child /> 传递了一个名为 "cusProp" 的prop , 即 <Child :cusProp="message" />

    然后在<Child />组件内部,通过props接收到了这个值,并通过v-bind:cusProp 将值绑定给了<input /> 元素。

    紧接着,我们给<input /> 元素设定了一个input 监听事件, 当输入时,触发该事件,然后将当前值通过this.$emit('cusProp',e.target.value) 触发了一个我们自定义命名为"cusProp"的自定义事件,以参数的形式,将变化后的值逆向传递(子传父)给了父组件。 在父组件中接收到变化后的值,然后通过$event 将值赋给了绑定的 message

    从而实现了自定义的双向绑定。

    实际上,上边这个过程,可以简化为一个vue为我们预定义实现的v-model, 但是不能直接替换,我们需要做一些简单的处理。这就涉及到了自定义v-model

    2. 自定义v-model

    2.1 v-model 语法糖, 以及最简单的自定义v-model

    首先,我们仿照着vue文档的举例,尝试去理解需要自定义v-model的使用场景。

    文档中有这样一段描述很重要

    一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件

    文档中的这段话极为概要,但是这句话蕴含了很重要的一些细节:

    实际,当你使用v-model 的时候,默认是,是传递的名为valueprop ,且$emit触发的自定义事件的事件名是input

    而回头看看我们刚才写的组件:

    父组件:

     <Child :cusProp="message" @cusEvent="message = $event" />
    

    子组件:

    <input type="text" :value="cusProp" @input="onInputChange">
    ...
    props:["cusProp"]
    ...
    onInputChange(e) {
        this.$emit('cusEvent', e.target.value)
    }
    

    我们默认传递的prop值名为"cusProp", 即v-bind:cusProp , 且$emit触发的自定义事件名为cusEvent 。 并不满足能直接写作v-model 的形式其前提条件。 所以我们不能直接替换。

    我们需要做一些简单的变化:

    父组件:

    <template>
      <div>
        <Child :value="message" @input="message = $event" /> <!--改动行-->
        文字:{{message}}
      </div>
    
    </template>
    <script>
    import Child from "./comps/child.vue"
    export default {
      components: {
        Child
      },
      data() {
        return {
          message: 'init default'
        }
      }
    }
    </script>
    

    子组件:

    <template>
      <div>
        this is child comp
        <input type="text" :value="value" @input="onInputChange"> <!--改动行-->
      </div>
    </template>
    <script>
    export default {
      props:["value"],// --改动行--
      methods: {
        onInputChange(e) {
          this.$emit('input', e.target.value) //--改动行--
        }
      }
    }
    </script>
    

    我们把prop 值的改为了value, 把$emit 触发的事件改为了input 现在,我们就能写作v-model 的 形式了,保持子组件不变,直接替换父组件中即可:

    <Child v-model="message" />
    

    自此,我们便能够理解,为什么说v-model 实际上就是props + $emit 自定义事件的语法糖 。

    2.2 通用自定义v-model

    上边的示例中,我们由于不满足先是利用了props 父传子,和自定义事件的子传父,手动实现了一个数据流的双向绑定。

    紧接着,我们介绍了v-model 的实质,就是props + 自定义事件 的语法糖。 然后我们期望将我们自己的手动实现,简化成v-model语法糖的形式。

    文档告诉我们,需要满足两个基本的默认条件:

    1. prop 名默认须为value
    2. $emit 触发的自定义事件名默认须为input

    而我们的手动实现起初并不满足要求(prop ---- cusProp, $emit ---- cusEvent), 所以我们做了部分修改,以满足默认的条件。 从而实现了将手动实现,转换成了v-model语法糖的形式。

    但是,这里有一个问题,就是v-model 默认的两个条件,会对我们有着很大的限制,这里封装的是一个<input/>输入框,以value prop值,以input 作为自定义事件名,本身是合乎习惯的,但是,日常开发中,我们不可能只封装一个输入框,可不能所有的自定义v-model 组件,都以value 传递,自定义事件一定名为input ,这显然是不合理的,也有违“自定义事件” 。我们开发工作中,可能更多的需要自定义指定prop名,和自定义事件。 为了更好的说明这个问题,解决通用性,下面我们通过一个示例来加深了解:

    这里之所以要着重强调,是因为很容易出错,这个文档中说的input 事件到底指的是,自定义子组件中元素的监听事件名为input,还是说$emit触发的事件名为input。 以上就是为了强调,是后者,是$emit触发的事件名,默认情况下,必须为input。 尽管它是自定义事件名,这也是之所以容易出错的地方。

    这里我们同样使用<input/ 这个元素,但是,不再用输入框了(type="text") ,我们将其指定为一个checkbox 看看会怎么样呢?

    <!--Father Component-->
    <template>
      <div>
        <Child :cusProp="status" @cusEvent="status = $event" />
        状态:{{status}}
      </div>
    
    </template>
    <script>
    import Child from "../cusVModelcheckBox/comps/child.vue"
    export default {
      components: {
        Child
      },
      data() {
        return {
          status: true
        }
      }
    }
    </script>
    
    <!-- Child Component-->
    <template>
      <div>
        this is child comp
        <input type="checkbox" :checked="cusProp" @change="onChange">
      </div>
    </template>
    <script>
    export default {
      props:["cusProp"],
      methods: {
        onChange(e) {
          this.$emit('cusEvent', e.target.checked)
        }
      }
    }
    

    image-20210824220413039

    一样的,如果此时,你想写作v-model语法糖的形式。就需要想刚才那样做一些改动:

    父组件:

    <template>
      <div>
        <Child v-model="status" /> <!--改动行-->
        状态:{{status}}
      </div>
    
    </template>
    <script>
    import Child from "../cusVModelcheckBox/comps/child.vue"
    export default {
      components: {
        Child
      },
      data() {
        return {
          status: true
        }
      }
    }
    </script>
    

    子组件:

    <template>
      <div>
        this is child comp
        <input type="checkbox" :checked="value" @change="onChange"> <!--改动行-->
      </div>
    </template>
    <script>
    export default {
      props:["value"], //--改动行--
      methods: {
        onChange(e) {
          this.$emit('input', e.target.checked) //--改动行--
        }
      }
    }
    

    现在,由于这是一个checkbox ,我们可以放大刚才所描述的限制了。 你莫名奇妙的加上接受了一个名为value 的prop, 以及通过$emit 触发了一个莫名其妙的input自定义事件。 尽管它能够如期的正常工作。 当代码量多了之后, 你会发现这种组件异常难以维护。

    那么到底该怎么解决这样一种场景呢?

    其实非常简单 , 只需要指定一个model 对象属性即可:

    我们只需要在刚才的基础上,在<Child/>组件中指定如下model配置加以稍微改动即可:

    <template>
      <div>
        this is child comp
        <input type="checkbox" :checked="cusProp" @change="onChange">	<!--改动行-->
      </div>
    </template>
    <script>
    export default {
      props:["cusProp"], 	//改动行 //当然一般直接写父组件v-model的变量名, 这里为了说明是任意名所以 写了个cusProp
      model:{				//改动行
        prop:'cusProp', 	//改动行
        event:'cusEvent'	//改动行
      },//改动行
      methods: {
        onChange(e) {
          this.$emit('cusEvent', e.target.checked)	//改动行
        }
      }
    }
    

    vue 为我们提供了一个名为model的实例配置项, 它可以指定一个任意的变量名,用于接受父组件中v-model 的传递值, 还可以指定一个任意的事件名, 用以"代理", $emit的触发事件.

    这样,就解决了难以后期维护的问题,使得有双向绑定需求的组件封装更加的通用.

    3. 总结

    所以,总结一下。

    什么情况下需要自定义v-model

    1. 当有自定义组件的双向数据流的需求的时候,都可以自定义v-model 来达成目的。

      1. 其中,什么时候需要配置 model 属性?

        当默认通过v-bind prop传递到自定义组件的变量名不是默认的value或者 触发自定义事件的事件名不为input 的时候。

      2. 什么时候不需要配置model 属性?

        当满足默认的v-model规则时,即 prop传递到自定义组件的变量名为value 触发自定义事件的事件名为input 的时候,不需要指定model属性配置。可直接使用v-model 这种情况比较少见,基本仅当自定义组件是为了扩展type="text"<input/> 元素时才符合条件。

    特别注意的一点:

    自定义事件内部,可以通过任意事件去触发$emit ,但是一般是通过DOM监听事件,例如@change@input@click,等等。 但是默认情况下,如果不配置model实例配置,加以指明,$emit 触发的事件名须是"input" 。 主要是不要混淆,这么默认情况下的约束规则,input 事件,指的是$emit触发的事件名,而不是自定义子组件内部触发$emit 的事件。

    通过model 实例配置,实际上帮我们解决的主要问题是日后的维护问题,和代码易读性。 它相当于背后帮我们自动将默认propvalue ,默认自定义事件为input 做了一层别名化处理(alias),从而让我们能够去自定义任何名称。

    4. 附加拓展,实践一个常见的v-model业务需求

    【需求:】

    假设现在有这样一个需求(基于antdv)

    image-20210824222912833

    有这样一个区域级联选择器,我希望,我能从父组件中给它一个初始值cascaderSelected:"浙江/杭州"。 在级联选择器值变换以后,这个cascaderSelected值响应式的变化。 要求利用v-model 实现,从而让代码简洁高效。

    实现:

    父组件:

    <template>
      <div>
        <cus-area-cascader v-model="cascaderSelected"/>
        当前选中区域:{{cascaderSelected}}
      </div>
    
    </template>
    <script>
    import CusAreaCascader from "../cusVModelPractice/comps/CusAreaCascader.vue"
    export default {
      components: {
        CusAreaCascader
      },
      data() {
        return {
          cascaderSelected: ['zhejiang', 'hangzhou','xihu']
        }
      }
    }
    </script>
    

    子组件:

    <template>
      <a-cascader :options="options" :value="onPropHandle" placeholder="Please select" @change="onChange" />
    </template>
    <script>
    export default {
      props:['onPropHandle'],
      model:{
        prop:'onPropHandle',
        event:'onChangeHandle'
      },
      data() {
        return {
          options: [
            {
              value: 'zhejiang',
              label: 'Zhejiang',
              children: [
                {
                  value: 'hangzhou',
                  label: 'Hangzhou',
                  children: [
                    {
                      value: 'xihu',
                      label: 'West Lake',
                    },
                  ],
                },
              ],
            },
            {
              value: 'jiangsu',
              label: 'Jiangsu',
              children: [
                {
                  value: 'nanjing',
                  label: 'Nanjing',
                  children: [
                    {
                      value: 'zhonghuamen',
                      label: 'Zhong Hua Men',
                    },
                  ],
                },
              ],
            },
          ],
        };
      },
      methods: {
        onChange(value) {
          this.$emit('onChangeHandle',value)
        },
      },
    };
    </script>
    
    

    image-20210824224431745

    目标达成。

  • 相关阅读:
    MySQL服务端恶意读取客户端文件漏洞 (DDCTF2019和国赛均涉及到这个漏洞)
    (转载)基于BIGINT溢出错误的SQL注入
    程序逻辑问题
    ansible笔记
    centos6 sersync2使用
    vsftpd服务
    rsync和rsync后台模式
    mysql5.6和5.7安装 centos
    mysql5.7-my.cnf
    bind-dns服务器搭建
  • 原文地址:https://www.cnblogs.com/jaycethanks/p/15182919.html
Copyright © 2011-2022 走看看