vue父子组件传值的问题,父子组件如何通讯?

要实现的功能:

  1. 点击【显示子组件】按扭,子组件显示----已实现

  2. 点击【关闭】,子组件关闭----未实现;第一次点击报underfind错误?,并且子组件没有隐藏

  3. 点击【确认】按钮把input中的值传到父组件的 {{text}} ------未实现

clipboard.png

clipboard.png

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>   
    <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>

</head>
<body>
<template id="aa">
    <div>
        <hr>
       我是子组件2
        <input type="text" v-model="text2">
        <button @click="close()">关闭</button>
        <button @click="affirm()">确认</button>
    </div>
</template>


<div id="box">
    <p>
        我是父组件:{{text}}
        <button @click="showChild()">显示子组件</button>
    </p>

    <child-page  v-show="pageSwitch" :pageSwitch2="pageSwitch" :text2="text"></child-page>
</div>
<script>
    new Vue({
        el:"#box",
        data:{
            text:"hello",
            pageSwitch:false
        },
        methods:{
            showChild:function(){
                this.pageSwitch=true;
            }
        },
        components:{
            "child-page":{
                template:"#aa",
                data:function(){
                    return{
                        childmsg:"子组件信息"
                    }
                },
                props:['pageSwitch2',"text2"],
                methods:{
                    close:function(){
                        console.log(this.pageSwitch2);
                        console.log(this.text2);
                        this.pageSwitch2=false;
                    },
                    affirm:function(){
                        console.log(this.pageSwitch2);
                        console.log(this.text2);
                    }
                }
            }
        }
    })
</script>
</body>
</html>
阅读 5.3k
8 个回答

用vuex吧,也就是单一事件中心管理组件通信

你报错的原因应该是你通过子组件修改了父组件的数据,这在vue2.0是不允许的(1.0可以),你要是想只是不报错,可以使用 mounted中转,但解决不了实际问题。所以可以父组件每次传一个对象给子组件, 对象之间引用

data:{
        parentData:{
            text:"hello",
            pageSwitch:false
        }  
    }

像上面这样,父组件传递数据对象给子组件,子组件修改数据对象里的内容。

父子组件的通信有自带的方法 诸如emit props等
但是我推荐你使用状态管理vuex 把vuex加入项目会使得整个项目管理和数据传递变得方便
当然了 如果你只是简单应用可以不这样做

那个报错的意思是,你不要直接修改父组建传过来的变量的值,这样容易前后不统一,
建议你在子组建建一个变量 或者用计算属性

子组件往父组件传递消息需要使用事件

子组件向父组件通信,可使用$emit、vuex、或eventBus等,根据题主描述的问题,建议使用$emit方法。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>   
    <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>

</head>
<body>
<template id="aa">
    <div>
        <hr>
       我是子组件2
        <input type="text" v-model="text2">
        <button @click="close()">关闭</button>
        <button @click="affirm()">确认</button>
    </div>
</template>


<div id="box">
    <p>
        我是父组件:{{text}}
        <button @click="showChild()">显示子组件</button>
    </p>

    <child-page  v-show="pageSwitch" :pageSwitch2="pageSwitch" :text2="text"></child-page>
</div>
<script>
    new Vue({
        el:"#box",
        data:{
            text:"hello",
            pageSwitch:false
        },
        methods:{
            showChild:function(){
                this.pageSwitch=true;
            }
        },
        components:{
            "child-page":{
                template:"#aa",
                data:function(){
                    return{
                        childmsg:"子组件信息"
                    }
                },
                props:['pageSwitch2',"text2"],
                methods:{
                    close:function(){
                        console.log(this.pageSwitch2);
                        console.log(this.text2);
                        this.$emit("pageSwitch2",false);
                    },
                    affirm:function(){
                        console.log(this.pageSwitch2);
                        console.log(this.text2);
                    }
                }
            }
        }
    })
</script>
</body>
</html>

ps:如题主方法,点击父组件方法隐藏子组件,虽然可以实现隐藏子组件的功能,但实际上还是控制父组件中的数据,并没有实现父子组件的通信,建议题主可以多了解props和emit等常用方法。

<script>
    //父组件
    Vue.component('father', {
        template: `
            <div class="fatherBox">
                <div class="message" v-html="fatherData"></div>
                <button @click="changeChild">父组件按钮</button>
                <child 
                    :show="forChild" 
                    @childHandle="doChildCommand"
                ></child>
            </div>
        `,
        data: function () {
            return {
                fatherData: '父组件数据',
                forChild: '父组件传给子组件的数据'
            }
        },
        methods: {
            changeChild: function () {
                this.fatherData = '命令来源:<span>父组件</span>';
                this.forChild = '父组件改变了传给子组件的数据';
            },
            doChildCommand: function (a,b) {
                this.fatherData = '命令来源:<span>子组件</span>';
                this.forChild = '子组件请求父组件改变了这个值。附带参数 <span>a->'+ a +', b->' + b +'</span>';
            }
        }
    })
 
    //子组件
    Vue.component('child', {
        props: {
            show: {
                type: String,
                default: '我是子组件'
            }
        },
        template: `
            <div class="childBox">
                <div class="message" v-html="show"></div>
                <button @click="changeFather">子组件按钮</button>
            </div>
        `,
        methods: {
            changeFather: function () {
                //通知父组件的一个函数
                //第一个参数为与父组件约定好的函数
                //后面的参数是递数据,可以为多个
                this.$emit('childHandle','我是数据一','我也是数据')
            }
        }
    })
     
    new Vue({
        template: '<father></father>',
        el: '#app'
    })
</script>

其实楼上的方法都是舍近求远了,用$emit可以解决问题,
但是$emit的局限也很大,比如通过slot传进来的子组件就没办法使用$emit
高效而且能够实现解耦的做法:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>   
    <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>

</head>
<body>
<template id="aa">
    <div>
        <hr>
       我是子组件2
        <input type="text" v-model="text2">
        <button @click="close()">关闭</button>
        <button @click="affirm()">确认</button>
    </div>
</template>


<div id="box">
    <p>
        我是父组件:{{text}}
        <button @click="showChild()">显示子组件</button>
    </p>

    <child-page  v-show="pageSwitch" :pageSwitch2="pageSwitch" :text2="text" :close="close" :affirm="affirm"></child-page>
</div>
<script>
    new Vue({
        el:"#box",
        data:{
            text:"hello",
            pageSwitch:false
        },
        methods:{
            showChild:function(){
                this.pageSwitch=true;
            },
            close:function(){
                console.log(this.pageSwitch2);
                console.log(this.text2);
                this.pageSwitch2=false;
            },
            affirm:function(){
                console.log(this.pageSwitch2);
                console.log(this.text2);
            }
        },
        components:{
            "child-page":{
                template:"#aa",
                data:function(){
                    return{
                        childmsg:"子组件信息"
                    }
                },
                props:['pageSwitch2',"text2","close","affirm"],
            }
        }
    })
</script>
</body>
</html>

整体思路的就是把close和affirm(拼写错误,其实是confirm)两个方法移动到父组件,
然后在父组件的模板里把这两个方法传给子组件。
解释:根据面向对象的“开闭原则”,要想实现通过继承子组件来修改内部的逻辑,必然要对外暴露使用关心的接口

感谢各位,效果已经实现了

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>

</head>
<body>
<template id="aa">
    <div>
        <hr>
       我是子组件2
        <input type="text" v-model="text2b">
        <button @click="close()">关闭</button>
        <button @click="affirm()">确认</button>
    </div>
</template>

<div id="box">
    <p>
        我是父组件:{{text}}
        <button @click="showChild()">显示子组件</button>
    </p>
    <child-page  v-show="pageswitch" :pageswitch2="pageswitch" :text2="text" v-on:close2="close()" v-on:affirm2="affirm()" ref="childpage"></child-page>
</div>
<script>
    new Vue({
        el:"#box",
        data:{
            text:"hello",
            pageswitch:false
        },
        methods:{
            showChild:function(){
                this.pageswitch=true;
            },
            close:function(){
                this.pageswitch=false;
            },
            affirm:function(){
                this.text=this.$refs.childpage.text2b;  //父组件获取子组件的值,使用this.$refs
            }
        },
        components:{
            "child-page":{
                template:"#aa",
                data:function(){
                    return{
                        childmsg:"子组件信息",
                        text2b:""
                    }
                },
                props:['pageswitch2',"text2"],  //子组件获取父组件的值,使用props
                methods:{
                    close:function(){
                        this.$emit("close2");   //子组件事件触发父组件的事件,使用 this.$emit
                    },
                    affirm:function(){
                        this.$emit("affirm2");
                    }
                },
                created:function(){
                    this.text2b=this.text2;
                }
            }
        }
    })
</script>
</body>
</html>

升级版代码功能更多:
支持多个事件

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>
</head>
<style>
    .reply{
        font-size: 12px;
        background: #d0d0d0;
        width: 300px;
        padding: 5px;
    }
</style>

<body>
<template id="aa">
    <div>
        <hr>
        <input type="text" v-model="reply2b" placeholder="请输入评论内容">
        <button @click="close()">关闭</button>
        <button @click="affirm()">确认</button>
    </div>
</template>

<div id="box">
    <ul>
        <li  v-for="item in items" @click="showChild(item)">
            {{item.title}}
            <p class="reply" v-show="item.reply">{{item.reply}}</p>
        </li>
    </ul>

    <child-page  v-show="pageswitch" :pageswitch2="pageswitch"  v-on:close2="close()" v-on:affirm2="affirm()" ref="childpage"></child-page>
</div>
<script>
    new Vue({
        el:"#box",
        data:{
            pageswitch:false,
            items:[
                {
                    "title":"标题一",
                    "reply":"我是评论一"
                },
                {
                    "title":"标题二",
                    "reply":""
                }
            ],
            item:""
        },
        methods:{
            showChild:function(item){
                this.pageswitch=true;
                this.$refs.childpage.init(item);   //父组件触发子组件的事件this.$refs;
                this.item=item;
            },
            close:function(){
                this.pageswitch=false;
            },
            affirm:function(){
                this.item.reply=this.$refs.childpage.reply2b;  //父组件获取子组件的值,使用this.$refs
            }
        },
        components:{
            "child-page":{
                template:"#aa",
                data:function(){
                    return{
                        childmsg:"子组件信息",
                        reply2b:""
                    }
                },
                props:['pageswitch2'],  //子组件获取父组件的值,使用props
                methods:{
                    close:function(){
                        this.$emit("close2");   //子组件事件触发父组件的方法,使用 this.$emit
                    },
                    affirm:function(){
                        this.$emit("affirm2");
                    },
                    init:function(item){
                        this.reply2b=item.reply;
                    }
                }
            }
        }
    })
</script>
</body>
</html>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题