Liaoct / blog

在这里记录一些个人经验与思考
22 stars 2 forks source link

Vue组件间的通信方案 #26

Open ghost opened 5 years ago

ghost commented 5 years ago

Vue组件间的通信方案

Props与事件机制

适用于父子组件之间通信。

通过props向子组件传递数据:

<template>
    <bar :name="name"></bar>
</template>

<script>
// foo.vue
import Bar from './bar';

export default {
    name: 'foo',
    components: [Bar],
    data() {
        return {
            name: 'name'
        };
    }
};
</script>

传递数据之前,需要在子组件定义中声明props:

// bar.vue
export default {
    name: 'bar',
    props: {
        name: String
    }
}

通过事件从子组件向父组件抛出数据:

// bar.vue
export default {
    name: 'bar',
    props: {
        name: String
    },
    methods: {
        changeName(newName) {
            this.name = newName;
            this.$emit('change', this.name); // 抛出change事件
        }
    }
}

在父组件中监听子组件抛出的事件:

<template>
    <bar :name="name" @change="handleChange"></bar>
</template>

<script>
// foo.vue
import Bar from './bar';

export default {
    name: 'foo',
    components: [Bar],
    data() {
        return {
            name: 'name'
        };
    },
    methods: {
        handleChange(val) {
            this.name = val;
        }
    }
};
</script>

对于如上示例,还可以使用v-modle.sync进行改写,请读者自行实现。

Bus机制

适用于任何组件结构之间的通信,基本原理也是事件机制。

创建一个全局Bus:

// bus.js
import Vue from 'vue';

export default new Vue();

在事件发起组件中,通过bus发起事件:

// bar.vue
import Bus from './bus';

export default {
    name: 'bar',
    mounted() {
        Bus.$emit('change', newVal) // 通过Bus发起事件,并传递数据
    }
};

在事件目标组件中,通过bus接受事件:

// foo.vue
import Bus from './bus';

export default {
    name: 'foo',
    created() {
        Bus.$on('change', newVal => { console.log(newVal); }) // 接受到数据
    }
};

Bus就是一个vue实例vue实例实现了事件机制,因此我们可以通过它来做中间人,通过中间人传递数据。

使用Vuex

Vuex是一个独立的状态管理工具,它可用在任意组件结构之间通信。

将共享状态放入vuex:

const CHANGE_NAME = 'CHANGE_NAME';

{
    state: { name: 'name' },
    getters: {
        name: state => state.name
    },
    mutations: {
        [CHANGE_NAME](state, payload) {
            state.name = payload.name;
        }
    },
    actions: {
        changeName({ commit }, payload) {
            commit(CHANGE_NAME, payload)
        }
    }
}

vuex获取数据:

<template>
    <span> {{ name }}</span>
</template>

<script>
// foo.vue
import { mapGetters } from 'vuex';

export default {
    name: 'foo',
    computed: {
        ...mapGetters(['name'])
    }
};
</script>

通过vuex action改变数据:

// bar.vue
import { mapActions } from 'vuex';

export default {
    name: 'bar',
    mounted() {
        this.changeName({ name: newVal });
    },
    methods: {
        ...mapActions(['changeName'])
    }
};

provide与inject

适用于深层次嵌套组件中,祖先组件向子孙组件传递数据。

祖先组件使用provide提供数据:

// foo.vue
export default {
    name: 'foo',
    data() {
        return {
            name: 'name'
        };
    },
    provide() {
        return {
            name: this.name
        };
    }
};

子孙组件使用inject注入数据:

// bar.vue
<template>
    <span>{{ name }}</span>
</template>

<script>
export default {
    name: 'bar',
    inject: ['name']
};
</script>

祖先组件与子孙组件的嵌套关系:

<foo>
    ...
    <other-component>
        <bar></bar>
    </other-component>
    ...
</foo>

事件广播机制

适用于祖先组件向子孙组件传递数据,且所有后代组件均会收到广播。

首先实现一个事件发射与广播mixin:

// emitter.js
function broadcast(componentName, eventName, params) {
    this.$children.forEach(child => {
        var name = child.$options.componentName;

        if (name === componentName) {
            child.$emit.apply(child, [eventName].concat(params));
        } else {
            broadcast.apply(child, [componentName, eventName].concat([params]));
        }
    });
}
export default {
    methods: {
        // 向指定的祖先组件发射事件
        dispatch(componentName, eventName, params) {
            var parent = this.$parent || this.$root;
            var name = parent.$options.componentName;

            while (parent && (!name || name !== componentName)) {
                parent = parent.$parent;

                if (parent) {
                    name = parent.$options.componentName;
                }
            }
            // 找到指定的祖先组件,触发其$emit方法,传递事件名和参数
            if (parent) {
                parent.$emit.apply(parent, [eventName].concat(params));
            }
        },
        // 向所有指定的子孙组件广播事件
        broadcast(componentName, eventName, params) {
            broadcast.call(this, componentName, eventName, params);
        }
    }
};

祖先组件中引入mixin,通过广播传递数据:

// foo.vue
import emitter from './emitter';

export default {
    name: 'foo',
    mixins: [emitter],
    data() {
        return {
            name: 'name'
        };
    },
    mounted() {
        this.broadcast('bar', 'changeName', newVal);
    }
};

子孙组件中接受广播的事件:

// bar.vue
export default {
    name: 'bar',
    created() {
        this.$on('changeName', newVal => console.log(newVal))
    }
};

事件调度(dispatch)机制

适用于后代组件祖先组件派遣数据。

子孙组件引入mixin,向祖先组件派遣事件:

// bar.vue
import emitter from './emitter';

export default {
    name: 'bar',
    mixins: [emitter],
    mounted() {
        this.dispatch('foo', 'changeName', newVal);
    }
};

祖先组件中接受来自子孙组件的数据:

// foo.vue
export default {
    name: 'foo',
    created() {
        this.$on('changeName',newVal => console.log(newVal));
    }
};