怎么实现父子组件的数据传递、事件传递,有哪几种方式

一、父获取子组件数据事件可以通过下面几种方式:

1、通过给子组件绑定ref属性,获取子组件实例

2、通过this.$children获取子组件实例

3、通过vuex完成父子组件数据事件通信
复制代码

二、子获取父组件数据事件可以通过下面几种方式:

1、通过props传递父组件数据、事件, 或者通过$emit和$on实现事件传递

2、通过ref属性,调用子组件方法,传递数据;通过props传递父组件数据、事件, 或者通过$emit和$on实现事件传递

3、通过this.$parent.$data或者this.$parevent._data获取父组件数据;通过this.$parent执行父组件方法

4、通过vuex完成父子组件数据事件通信
复制代码
<div class="demo-block">
    我是父组件内容,我有一个子组件名字叫{{childName1}},这是通过ref获取的内容; <br>
    我是父组件内容,我有一个子组件名字叫{{childName2}},这是通过children获取的内容; <br>
    <component-a ref="childA" :message1="msg1" :message2="msg2" :fn="setFn"></component-a><br>
    <button @click="getName1()">refs获取子组件名称</button>
    <button @click="getName2()">children获取子组件名称</button>
    <button @click="setChild">触发子组件事件写入数据</button>
</div>
复制代码
<script>
    var componentA = {
        name: 'child1',
        template: `<div><div>我是子组件child:{{message1}}-{{message2}},写入数据{{con}}</div><button @click="fn1">通过props调用父组件方法</button> <button @click="fn2">通过$parent调用父组件方法</button></div>`,
        data() {
            return {
                child: true,
                name: 'child1',
                con:''
            }
        },
        props: ['message1', 'message2', 'fn'],
        methods: {
            fn1() {
                typeof this.fn === 'function' && this.fn();
            },
            fn2() {
                this.$parent && this.$parent.setFn();
            },
            init(params) {
                this.con = params.msg1 + params.msg2;
                alert('我是子组件')
            }
        }
    };
    export default {
        components: {
            componentA
        },
        data() {
            return {
                msg1: 'hello',
                msg2: 'world',
                prevent: true,
                childName1: '',
                childName2: ''
            };
        },
        methods: {
            getName1() {
                this.childName1 = this.$refs.childA.name;
            },
            getName2() {
                this.childName2 = this.$children[0].name;
            },
            setChild() {
                this.$children[0].init({
                    msg1: 'world',
                    msg2: 'hello',
                });
            },
            setFn() {
                alert('我是父组件')
            }
        }
    }
</script>
复制代码


怎么实现兄弟组件之间的数据传递、事件传递,有哪几种方式

1、利用Vuex存储数据, 配合父组件watch进行事件监听

2、利用bus中央开关总线,挂载全局事件

3、利用$parent进去数据传递, $parent.$children调用兄弟组件事件
复制代码
<style>
    .tab-list {
        border-bottom: 1px solid #ccc;
        padding-bottom: 10px;
    }
    .tab-item {
        display: inline-block;
        width: 100px;
        text-align: center;
        border-right: 1px solid #ccc;
    }
    .tab-item.active {
        color: red;
    }
</style>
<div class="demo-block">
    <component-b ref="childB" :listType.sync='transitType'></component-b><br>
    <component-c ref="childC"></component-c>
</div>
复制代码
<script>
    var componentB = {
        template: `<div>
                      <div class="tab-list">
                        <span class="tab-item" v-for="(item, index) in list" @click="changeType(item.type)" :class="{'active': item.type === type}">{{item.value}}</span>
                      </div>
                   </div>`,
        data() {
            return {
                type: 0,
                list: [{value: '新闻', type: 0}, {value: '汽车', type: 1}, {value: '娱乐', type: 2}]
            }
        },
        props: ['listType'],
        methods: {
            changeType(type) {
                this.type = type;
                if(type === 0) { // 使用watch开关通知
                    this.$emit('update:listType', type)
                } else if(type === 1) { // 使用中央总线;
                    /*挂载全局触发事件*/
                    window.Vue.$emit('switchType', type);
                    this.$emit('update:listType', type)
                } else { // 使用$parent.$children
                    // this.$parent.$children[1].$options.name 可以获取组件名称
                    // this.$parent.$children[1].$options._componentTag 可以获取局部注册的组件标签名
                    this.$parent.$children[2].switchType && this.$parent.$children[2].switchType(type)
                    this.$emit('update:listType', type)
                }
            }
        }
    };
    var componentC = {
        name: 'childC',
        template: `<div>
                        <div v-for="(item, index) in listInfo[listType]">{{item}}</div>
                   </div>`,
        data() {
            return {
                listType: 0,
                listInfo: {
                    0: ['新闻1', '新闻2', '新闻3'],
                    1: ['汽车1', '汽车2', '汽车3'],
                    2: ['娱乐1', '娱乐2', '娱乐3']
                }
            }
        },
        props: [],
        methods: {
            switchType(type) {
                this.listType = type;
            }
        },
        mounted() {
            /*挂载全局触发事件*/
            window.Vue.$on('switchType', (type) => {
                console.log(type, 'type');
                this.switchType(type);
            });
        }
    };
    export default {
        components: {
            componentB,
            componentC
        },
        data() {
            return {
                transitType: 0, // 这是一个中转控制属性
            };
        },
        methods: {
        },
        mounted() {
            //
            this.transitType = this.$refs.childB.type;
            console.log(this.transitType)
        },
        watch: {
            transitType(newType, oldType) {
                if(newType === 0) {
                    this.$refs.childC.switchType && this.$refs.childC.switchType(newType);
                }
            }
        }
    }
</script>
复制代码


怎么实现祖孙组件之间的数据传递、事件传递,有哪几种方式

1、利用$attrs实现祖孙组件间的数据传递,$listeners实现祖孙组件间的事件监听
 $attrs包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

2、利用provide/inject 实现祖孙组件间的数据传递和事件监听
provide 和 inject 主要为高阶插件/组件库提供用例,允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

3、利用vuex传递存储数据,利用$parent、$children封装一套可向下广播事件,向上派发事件的方法;
复制代码

第一种实现方式

<style>
    .mask-bg {
        position: fixed;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, .3);
    }
    .alert {
        position: fixed;
        left: 50%;
        top: 50%;
        width: 300px;
        height: 200px;
        border-radius: 10px;
        text-align: center;
        background-color: #fff;
        transform: translate(-50%, -50%);
    }
</style>
<div class="demo-block">
    <div>
        <div @click="isShow = true" style="margin-bottom: 20px">显示弹层(第一种方式)</div>
        <my-mask :show="isShow" :title="title" :con="con" @on-submit="submit"></my-mask><br>
    </div>
</div>
复制代码
// 弹层组件(子组件的子组件)
var alert = {
    template: ` <div class="alert">
                     <h2>{{title}}</h2>
                     <div class="alert-con">
                          {{con}}
                     </div>
                     <button class="submit" @click="submit">确定</button>
                </div>`,
    data() {
        return {
        }
    },
    props: ['title', 'con'],
    methods: {
        submit() {
            this.$emit('on-submit')
        }
    }
};
// mask组件(子组件)
var myMask = {
    template: `<div class="mask" v-if="show">
                    <div class="mask-bg"></div>
                    <alert v-bind="$attrs" v-on="$listeners"></alert>
               </div>`,
    components: {
        alert
    },
    inheritAttrs: false,
    data() {
        return {
        }
    },
    props: ['show'],
    methods: {
    },
    watch: {
    }
};
export default {
    name: 'my-page',
    components: {
        myMask,
    },
    data() {
        return {}
            },
            isShow: false,
            title: '我是标题',
            con:  '我是内容',
        };
    },
    methods: {
        submit() {
            window.alert('关闭弹层');
            this.isShow = false;
        },
    }
}
复制代码

祖组件在传递数据给子组件的时候,如果子组件没有通过props接收祖组件传递的参数,那么这个数据会表现在子组件的根标签的属性上;通过设置 inheritAttrs 到 false,这些默认行为将会被去掉; 然后我们可以在子组件绑定v-bind="attrs",将这些特性传递给孙组件,孙组件通过props接收数据; 同理我们也可以通过listeners实现祖孙之间的事件监听器传递

第二种实现方式

<style>
.tab-list {
    border-bottom: 1px solid #ccc;
    padding-bottom: 10px;
}
.tab-item {
    display: inline-block;
    width: 100px;
    text-align: center;
    border-right: 1px solid #ccc;
}
.tab-item.active {
    color: red;
}
</style>
<div class="demo-block">
    <div>
        <div @click="changeCon()" style="margin-bottom: 20px">切换显示内容(第二种方式)</div>
        <my-con></my-con><br>
    </div>
</div>
复制代码
var myTable = {
    template: `<div>
                  <div class="tab-list">
                    <span class="tab-item" v-for="(item, index) in list" @click="switchType(item.type)" :class="{'active': item.type === store.state.type}">{{item.value}}</span>
                  </div>
               </div>`,
    data() {
        return {
            tableType: 0,
            list: [{value: '新闻', type: 0}, {value: '汽车', type: 1}, {value: '娱乐', type: 2}]
        }
    },
    inject: ['store'],
    methods: {
        switchType(value) {
            this.store.commit('type', value)
        }
    }
};
var myList = {
    template: `<div>
                    <div v-for="(item, index) in listInfo[store.state.type]">{{item}}</div>
               </div>`,
    data() {
        return {
            listType: 0,
            listInfo: {
                0: ['新闻1', '新闻2', '新闻3'],
                1: ['汽车1', '汽车2', '汽车3'],
                2: ['娱乐1', '娱乐2', '娱乐3']
            }
        }
    },
    inject: ['store'],
    props: [],
    methods: {
    },
    mounted() {
    }
};
var myCon = {
    template: `<div>
                  <my-table></my-table>
                  <my-list></my-list>
               </div>`,
    data() {
        return {
        }
    },
    components: {
        myTable,
        myList,
        mySearch
    },
    props: [],
    methods: {
    },
    mounted() {
    }
};

export default {
    name: 'my-page',
    components: {
        myCon,
    },
    provide() {
        return {
            store: this.store
        }
    },
    data() {
        return {
            store: {
                state: {
                    type: 0
                },
                commit (type, value) {
                    this.state[type] = value
                }
            },
        };
    },
    methods: {
        changeCon() {
            this.store.state.type ++;
            if(this.store.state.type > 2) this.store.state.type = 0;
        },
    },
    mounted() {
    }
}
复制代码

第三种实现方式(来自饿了么UI之前源码)


<div class="demo-block">
    <div>
        <input type="text" placeholder="搜索(第三种方式)" class="input" @input="changInput"><span v-if="!hasSearch" style="display:inline-block; margin-bottom: 20px">请输入内容</span>
        <my-con></my-con><br>
    </div>
</div>
复制代码
var myCon = {
    template: `<div>
                  <my-table></my-table>
                  <my-list></my-list>
                  <my-search></my-search>
               </div>`,
    data() {
        return {
        }
    },
    components: {
        myTable,
        myList,
        mySearch
    },
    props: [],
    methods: {
    },
    mounted() {
    }
};
var mySearch = {
    name: 'mySearch',
    template: `<div>
                    <p style="margin: 20px auto">{{content}}</p>
               </div>`,
    data() {
        return {
            content: '没有搜索内容'
        }
    },
    props: [],
    methods: {
        /*对多级父组件进行事件派发*/
        dispatch(componentName, eventName, params) {
            /*获取父组件,如果以及是根组件,则是$root*/
            var parent = this.$parent || this.$root;
            /*获取父节点的组件名*/
            var name = parent.$options.name;
            while (parent && (!name || name !== componentName)) {
                /*当父组件不是所需组件时继续向上寻找*/
                parent = parent.$parent;
                if (parent) {
                    name = parent.$options.name;
                }
            }
            /*找到所需组件后调用$emit触发当前事件*/
            if (parent) {
                parent.$emit.apply(parent, [eventName].concat(params));
            }
        },
    },
    mounted() {
        this.$on('on-input', (value) => {
            this.content = value;
            if(value == '') {
                this.content = '没有搜索内容';
                this.dispatch('my-page', 'no-data', false);
            }
        })
    }
};
function broadcast(componentName, eventName, params) {
  /*遍历当前节点下的所有子组件*/
  this.$children.forEach(child => {
    /*获取子组件名称*/
    var name = child.$options.name;

    if (name === componentName) {
      /*如果是我们需要广播到的子组件的时候调用$emit触发所需事件,在子组件中用$on监听*/
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      /*非所需子组件则递归遍历深层次子组件*/
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
    name: 'my-page',
    components: {
        myMask,
        myCon,
    },
    provide() {
        return {
            store: this.store
        }
    },
    data() {
        return {
            store: {
                state: {
                    type: 0
                },
                commit (type, value) {
                    this.state[type] = value
                }
            },
            isShow: false,
            title: '我是标题',
            con:  '我是内容',
            hasSearch: false
        };
    },
    methods: {
        submit() {
            window.alert('关闭弹层');
            this.isShow = false;
        },
        changeCon() {
            this.store.state.type ++;
            if(this.store.state.type > 2) this.store.state.type = 0;
        },
        changInput(e) {
            if(e.target.value) {
                this.hasSearch = true;
            }
            this.broadcast('mySearch', 'on-input', e.target.value)
        },
        /*对多级父组件进行事件派发*/
        dispatch(componentName, eventName, params) {
            /*获取父组件,如果以及是根组件,则是$root*/
            var parent = this.$parent || this.$root;
            /*获取父节点的组件名*/
            var name = parent.$options.name;
            while (parent && (!name || name !== componentName)) {
                /*当父组件不是所需组件时继续向上寻找*/
                parent = parent.$parent;
                if (parent) {
                    name = parent.$options.name;
                }
            }
            /*找到所需组件后调用$emit触发当前事件*/
            if (parent) {
                parent.$emit.apply(parent, [eventName].concat(params));
            }
        },
        broadcast(componentName, eventName, params) {
            broadcast.call(this, componentName, eventName, params);
        }
    },
    mounted() {
        this.$on('no-data', (value) => {
            this.hasSearch = value;
        })
    }
}
复制代码

broadcast通过递归遍历子组件找到所需组件广播事件,而dispatch则逐级向上查找对应父组件派发事件。 broadcast(componentName:(组件名),eventName:(事件名称)以及params:(数据))。根据componentName深度遍历子组件找到对应组件emit事件eventName。 dispatch(componentName:(组件名),eventName:(事件名称)以及params:(数据))。根据componentName向上级一直寻找对应父组件,找到以后emit事件eventName。
通过这种方式进行数据、事件传递,必须给组件命名name;

转载于:https://juejin.im/post/5b570924e51d45195f0b3bb3

Logo

前往低代码交流专区

更多推荐