【Vue】子组件如何监听父界面字段以及如何自己实现v-model双向绑定

by img Microanswer 创建时间:Jun 25, 2019 10:35:29 AM 

标签: vue v-model 双向绑定 事件



重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

在 Vue 项目开发中,Vue 框架的确给了我们许多便利,不用再频繁的获取某dom然后修改里面的内容,使用vue只需将字段重新赋值, 界面也会自动刷新。但有时候发现,我们给子组件的某个属性赋值后,界面却没有及时得到更新,出现这种现象的时候一刹那间是摸不着头脑的。

为了从头到尾把事情讲清除,咱们从子组件的编写开始。

一、建立子组件

我们这一节将建立一个实现数字增减的控件。它的外观看起来是这样的:图1。 新建 Vue 文件,使文件名(组件名)为:NumberMaker.vue, 开始干代码:

<!-- NumberMaker.vue -->
<template>
    <div class="numbermaker">
        <!-- 减按钮 -->
        <button @click="number--">-</button>
        <input min="0" max="99" type="number" v-model="number" autocomplete="off"/>

        <!-- 加按钮 -->
        <button @click="number++">+</button>
    </div>
</template>

<script>
    export default {
        data () {
            return {
                number: 0
            }
        }
    }
</script>

<style scoped>

    /* 加边框,圆角,内边距 */
    .numbermaker {
        border: 1px solid gray;
        display: inline-block;
        border-radius: 3px;
        padding: 3px 6px;
    }

    /* 去除输入框边框,限定宽度,文字居中 */
    input {
        outline: none;
        width: 30px;
        border: none;
        text-align: center;
    }

    /* 去掉输入框 type=number 时的上下箭头 */
    input::-webkit-outer-spin-button,
    input::-webkit-inner-spin-button {
        -webkit-appearance: none;
    }

    input[type="number"] {
        -moz-appearance: textfield;
    }
</style>

这个组件很简单,一个减按钮、一个数字输入框、一个加按钮即完成了这个组件的样式。由于这个组件是要实现对数字的增加和减少功能, 所以很容易想到,组件内自己应该维护一个 number 变量,所以,data 模块里定义了 number 属性。

二、加入界面

为了引发本文的问题,我们在界面上设定一个输入框,这个输入框可以随时输入任意的数字。下面列出界面的代码:

<!-- App.vue -->
<template>
    <div id="app">
    
        <!-- 界面上的可输入数字的输入框 -->
        <input v-model="myNum" type="number" autocomplete="off">
        <br/>
        <br/>
        
        <!-- 使用子组件 -->
        <number-maker/>
    </div>
</template>
<script>

    // 引入子组件
    import NumberMaker from './components/NumberMaker.vue'

    export default {
        components: {
            NumberMaker // 注册
        },
        data () {
            return {
                myNum: 0, // 页面上输入框的值
            }
        }
    }
</script>
<style>
    #app {
        text-align: center;
        margin-top: 60px;
    }
</style>

此时运行程序你应该看到这样的界面:

图2

要把页面上维护的 myNum 字段的值传入到子组件 NumberMaker 里面, 必然是要为子组件添加一个属性,刚才在定义组件是, 并没有想过要添加属性什么的,所以,再次修改组件的代码部分,加入 props 属性,我们就命名这个属性为 pNumber 好了。 修改组件代码如下:

<!-- NumberMaker.vue -->
<template>
    ...
</template>
<script>
    export default {
        
        // 新增属性节点
        props: {
            // 添加一个 pNumber 属性,该属性只能是数字,让界面可以传入数字
            pNumber: Number
        },
        data () {
            return {
                number: this.pNumber // 不再使用 固定值0, 而是使用属性具体的值。
            }
        }
    }
</script>
<style>
...
</style>

首先我们新增了 props 节点, 此节点的作用就是暴露组件支持的参数。然后我们将 data 模块中的数据 number 字段使用参数pNumber的值, 这样一来,组件在渲染时就可以接收到页面传递进入的值了。

为了体现效果,将页面中的字段 myNum 默认值修改为非零的其它值, 并将参数传递个子组件:

<!-- App.vue -->
<template>
    <div id="app">
    
        <!-- 界面上的可输入数字的输入框 -->
        <input v-model="myNum" type="number" autocomplete="off">
        <br/>
        <br/>
        
        <!-- 使用子组件,并传入参数 -->
        <number-maker :p-number="myNum"/>
    </div>
</template>
<script>

    // 引入子组件
    import NumberMaker from './components/NumberMaker.vue'

    export default {
        components: {
            NumberMaker // 注册
        },
        data () {
            return {
                myNum: 5, // 页面上输入框的值
            }
        }
    }
</script>
<style>
...
</style>

此时,刷新界面,可以看到,组件里的值已经按照设定的值进行了响应变化。

图3

三、重现问题

经过了上面一系列的常规操作,问题也应运而生了。修改输入框里的值,却不能同时更新到子组件里面的值:

图4

这个时候,你心里肯定就想,我要是能在子组件里监听到父界面里面这个 myNum 字段的变化,这不就能实现组件里的值 也随之变化嘛。这个想法非常好,但不得不告诉你,子组件是不能实现监听父界面的参数变化的。仔细想想,我作为子组件, 我怎么知道你父界面上有些什么东西,我只管好我自己的事情就好了。但是难道就不能实现这样的功能了吗?当然能实现了。

虽然子组件不能监听到父界面的东西,但是我可以使用另一种方法来实现这样的需求。

四、解决问题

我们在 NumberMaker 组件中使用了属性 pNumber ,虽然我子组件不知道父界面有什么东西,但是 pNumber 参数是我自己创建的,父界面要把参数传进来,就得通过这个字段,我不能监听父界面的参数,那我还不能监听我自己定义的参数 pNumber 吗,这个我总可以监听了吧。说干就干,现在来监听 pNumber 字段试试,修改 NumberMaker 组件的代码:

<!-- NumberMaker.vue -->
<template>
    ...
</template>
<script>
    export default {
        props: {
            pNumber: Number
        },
        watch: {

            // 监听参数pNumber, 并将新值赋给data里面的number字段。
            pNumber (newvalue) {
                this.number = newvalue;
            }
        },
        data () {
            return {
                number: this.pNumber
            }
        }
    }
</script>
<style>
...
</style>

添加了 watch 节点,加入了对参数 pNumber 的监听支持,并将值赋值给了组件的number字段。 这样一来,再看看界面的运行效果:

图5

cool! 这不就是想要的效果嘛。现在已经做到了修改父界面的内容,子组件同时做到响应的更新。 那如果更新子组件的内容,父界面的值会更新吗?先来看看会不会:

图6

看起来子组件内容变化并不能同步更新父界面的值。其实要实现组件里的字段更新通知到父界面, 可以使用 $emit 方法,将事件暴露给父界面,代码就像下面:

图7

效果:

图8

可以看到,让子组件的值变化暴露给父界面,使用 $emit 方法的确可以实现,不过在父界面中我们写了相应的 2 处代码来实现这个功能。我们能不能在自己的组件里实现像 input 组件那样的 v-model 功能呢, 最终达到,能在自己的组件上使用 v-model 功能呢?

当然能, 不过这就对 props 里的属性名和 $emit 出去的事件名有硬性要求了。要了解的关键点如下:

  1. 使用v-model传递给组件的数据,在组件内须使用属性字段 value 来接收。
  2. 要将数据可以同时对外输出,在 $emit 的时候,须将事件名固定为 input。

明白了这2点,就可以重新修改 NumberMaker 组件的代码:

<!-- NumberMaker.vue -->
<template>
    ...
</template>
<script>
    export default {
        props: {
            value: Number
        },
        watch: {

            // 监听参数value, 并将新值赋给data里面的number字段。
            value (newvalue) {
                this.number = newvalue;
            },
            
            // 监听字段number,并将变更事件发出,事件名为 input
            number (newvalue) {
                this.$emit('input', newvalue);
            }
        },
        data () {
            return {
                number: this.value
            }
        }
    }
</script>
<style>
...
</style>

由于 v-model 要求属性字段需要是 value,所以将组件里的 pNumber 修改为value,然后将监听 pNumber 的方法修改为监听 value 的方法,将 number 的变化事件通过 input 事件名称向外暴露, 这样就能在父界面不用再写一堆监听的代码了,只需要一个 v-model 即可实现。重新修改 App.vue 界面代码:

<template>
    <div id="app">
    
        <!-- 界面上的可输入数字的输入框 -->
        <input v-model="myNum" type="number" autocomplete="off">
        <br/>
        <br/>
        
        <!-- 使用子组件,直接使用v-model实现双向绑定,就如同上边的input那样 -->
        <number-maker v-model="myNum"/>
    </div>
</template>
<script>

    // 引入子组件
    import NumberMaker from './components/NumberMaker.vue'

    export default {
        components: {
            NumberMaker // 注册
        },
        // 不再需要监听方法了。
        // methods: {
        //     // 监听子组件值变化结果的方法
        //     onNumChange (val) {
        //         this.myNum = val;
        //     }
        // },
        data () {
            return {
                myNum: 5, // 页面上输入框的值
            }
        }
    }
</script>
<style>
...
</style>

修改引用组件处的代码,移除了原来的监听,移除了原来的 pNumber 参数,直接使用 v-model 将父界面的 myNum 放入,实现双向绑定。效果图:

图9

总结

这也是为啥,常常看到许多出名的vue组件库里面的组件里,参数里有个 index,而data里面又有一个 currentIndex, 或相似的有两个字段,不仅仅是因为要实现双向绑定的功能,还有一个原因就是,Vue组件里,不能直接修改 props 里面的属性内容,所以要定义组件自身使用的字段在data里,也要定义外界能传入的参数在props里。组件实现监听父界面 的内容,其实就是组件监听自己定义的props里面的属性来实现。永远不要去想直接监听父界面的参数,这有背于组件的概念。

本例涉及的项目你可以在这里下载到:百度网盘, 提取码:rhqe

全文完, 转载请注明出处。 对你有帮助?不如赞一个吧:
发表评论(发表评论需要登录,你现在还没有登录。)
你需要先登录才可以评论。

评论列表 (0条)