component间通信

深入LearningVue.jscomponent间通信 各种方式, Master父子component, 兄弟component and 跨级component 通信techniques

1. component间通信overview

in Vue.jsapplicationin, component间通信 is 指不同component之间传递data and event mechanism. Vueproviding了 many 种component间通信方式, 适用于不同 场景.

1.1 component间通信 场景

  • 父子component通信
  • 兄弟component通信
  • 跨级component通信
  • 任意component间通信

1.2 component间通信 方式

  • props (父向子传递data)
  • event (子向父传递data)
  • event总线 (任意component间通信)
  • Provide/Inject (祖先向 after 代传递data)
  • Vuex/Pinia (statusmanagementlibrary)
  • $parent/$children (直接访问componentinstance)
  • $refs (直接访问DOM or componentinstance)

2. 父子component通信

父子component is 最common componentrelationships, Vueproviding了props and eventmechanism来implementation父子component间 双向通信.

2.1 父向子传递data: props

父componentthroughprops向子component传递data, 子componentthroughprops选项接收data.

// 父component
var ParentComponent = {
    template: `
        <div class="parent">
            <h3>父component</h3>
            <p>父componentdata: {{ message }}</p>
            <ChildComponent :message="message" :count="count"></ChildComponent>
        </div>
    `,
    data: function() {
        return {
            message: 'Hello from parent',
            count: 100
        };
    },
    components: {
        'ChildComponent': ChildComponent
    }
};

// 子component
var ChildComponent = {
    template: `
        <div class="child">
            <h4>子component</h4>
            <p>父component传递 message: {{ message }}</p>
            <p>父component传递 计数: {{ count }}</p>
        </div>
    `,
    props: ['message', 'count']
};

2.2 子向父传递data: event

子componentthrough$emitmethod触发event, 父componentthroughv-on指令监听event来接收data.

// 父component
var ParentComponent = {
    template: `
        <div class="parent">
            <h3>父component</h3>
            <p>子component传递 data: {{ childData }}</p>
            <ChildComponent @child-event="handleChildEvent"></ChildComponent>
        </div>
    `,
    data: function() {
        return {
            childData: ''
        };
    },
    methods: {
        handleChildEvent: function(data) {
            this.childData = data;
            console.log('收 to 子component event:', data);
        }
    },
    components: {
        'ChildComponent': ChildComponent
    }
};

// 子component
var ChildComponent = {
    template: `
        <div class="child">
            <h4>子component</h4>
            <button @click="sendMessage">向父component发送message</button>
        </div>
    `,
    methods: {
        sendMessage: function() {
            // 触发自定义event, 传递data
            this.$emit('child-event', 'Hello from child');
        }
    }
};

2.3 父子component双向data绑定: v-model

v-model指令可以implementation父子component间 双向data绑定, 它practical on is props and event 语法糖.

// 父component
var ParentComponent = {
    template: `
        <div class="parent">
            <h3>父component</h3>
            <p>父componentdata: {{ message }}</p>
            <ChildComponent v-model="message"></ChildComponent>
        </div>
    `,
    data: function() {
        return {
            message: 'Hello'
        };
    },
    components: {
        'ChildComponent': ChildComponent
    }
};

// 子component
var ChildComponent = {
    template: `
        <div class="child">
            <h4>子component</h4>
            <input type="text" :value="value" @input="updateValue">
        </div>
    `,
    props: ['value'], // v-model默认usingvalueserving asprop
    methods: {
        updateValue: function(event) {
            // v-model默认监听inputevent
            this.$emit('input', event.target.value);
        }
    }
};

自定义v-model

可以throughmodel选项自定义v-model prop and event名.

// 子component
var ChildComponent = {
    template: `
        <div class="child">
            <h4>子component</h4>
            <input type="text" :value="modelValue" @input="updateValue">
        </div>
    `,
    model: {
        prop: 'modelValue', // 自定义prop名
        event: 'update:modelValue' // 自定义event名
    },
    props: ['modelValue'],
    methods: {
        updateValue: function(event) {
            this.$emit('update:modelValue', event.target.value);
        }
    }
};

3. 兄弟component通信

兄弟component之间没 has 直接 通信方式, 需要through父componentserving asin间媒介, or 者usingevent总线, statusmanagementlibraryetc.方式.

3.1 through父componentin转

兄弟componentthrough父componentserving asin间媒介来传递data.

// 父component
var ParentComponent = {
    template: `
        <div class="parent">
            <h3>父component</h3>
            <BrotherA @send-data="handleDataFromA"></BrotherA>
            <BrotherB :data="sharedData"></BrotherB>
        </div>
    `,
    data: function() {
        return {
            sharedData: ''
        };
    },
    methods: {
        handleDataFromA: function(data) {
            this.sharedData = data;
        }
    },
    components: {
        'BrotherA': BrotherA,
        'BrotherB': BrotherB
    }
};

// 兄弟componentA
var BrotherA = {
    template: `
        <div class="brother-a">
            <h4>兄弟componentA</h4>
            <button @click="sendToB">向兄弟componentB发送data</button>
        </div>
    `,
    methods: {
        sendToB: function() {
            this.$emit('send-data', 'Hello from Brother A');
        }
    }
};

// 兄弟componentB
var BrotherB = {
    template: `
        <div class="brother-b">
            <h4>兄弟componentB</h4>
            <p>来自兄弟componentA data: {{ data }}</p>
        </div>
    `,
    props: ['data']
};

3.2 event总线

event总线 is a simple release-subscribe模式, 适用于任意component间通信.

// creationevent总线
var bus = new Vue();

// componentA: releaseevent
var ComponentA = {
    template: `
        <div class="component-a">
            <h4>componentA</h4>
            <button @click="sendMessage">发送message</button>
        </div>
    `,
    methods: {
        sendMessage: function() {
            // releaseevent
            bus.$emit('custom-event', 'Hello from Component A');
        }
    }
};

// componentB: subscribeevent
var ComponentB = {
    template: `
        <div class="component-b">
            <h4>componentB</h4>
            <p>收 to  message: {{ message }}</p>
        </div>
    `,
    data: function() {
        return {
            message: ''
        };
    },
    created: function() {
        // subscribeevent
        var that = this;
        bus.$on('custom-event', function(data) {
            that.message = data;
        });
    }
};

注意

usingevent总线时, 需要注意 in component销毁时取消event监听, 避免memory泄漏.

created: function() {
    this.busListener = bus.$on('custom-event', function(data) {
        this.message = data;
    }.bind(this));
},

beforeDestroy: function() {
    // 取消event监听
    bus.$off('custom-event', this.busListener);
};

4. 跨级component通信

跨级component is 指父component 子component 子component, or 者更深层次 componentrelationships.

4.1 Provide/Inject API

Vue 2.2.0+ providing了Provide/Inject API, 允许祖先component向其所 has after 代component注入依赖, 无论component层次 has how deep.

// 祖先component
var AncestorComponent = {
    template: `
        <div class="ancestor">
            <h3>祖先component</h3>
            <ParentComponent></ParentComponent>
        </div>
    `,
    provide: function() {
        return {
            ancestorData: 'Hello from ancestor',
            ancestorMethod: this.ancestorMethod
        };
    },
    methods: {
        ancestorMethod: function() {
            console.log('This is a method from ancestor');
        }
    },
    components: {
        'ParentComponent': ParentComponent
    }
};

// 父component
var ParentComponent = {
    template: `
        <div class="parent">
            <h4>父component</h4>
            <ChildComponent></ChildComponent>
        </div>
    `,
    components: {
        'ChildComponent': ChildComponent
    }
};

// 子component ( after 代component) 
var ChildComponent = {
    template: `
        <div class="child">
            <h5>子component</h5>
            <p>来自祖先component data: {{ ancestorData }}</p>
            <button @click="callAncestorMethod">调用祖先component method</button>
        </div>
    `,
    inject: ['ancestorData', 'ancestorMethod'], // 注入祖先componentproviding 依赖
    methods: {
        callAncestorMethod: function() {
            this.ancestorMethod();
        }
    }
};

提示

Provide/Inject API主要用于 high 阶插件/componentlibrary Development, 不推荐 in 普通applicationinusing, 因 for 它会使component 依赖relationships变得不明确.

4.2 throughprops and event层层传递

可以throughprops将data from 祖先component层层传递给 after 代component, throughevent将data from after 代component层层传递给祖先component. 但这种方式 in component层次较深时会变得繁琐.

5. statusmanagementlibrary

for 于 complex application, Vue推荐usingstatusmanagementlibrary (such asVuex or Pinia) 来managementcomponent间共享 status.

5.1 VuexIntroduction

Vuex is Vue 官方statusmanagementlibrary, 它providing了一个集in式storemanagementapplication 所 has component status, 并以相应 规则保证status以一种可预测 方式发生变化.

Vuex core concepts

  • State: storeapplicationstatus object
  • Getter: from Stateinfork出 new status
  • Mutation: modifyState 唯一方式, 必须 is synchronizationfunction
  • Action: processingasynchronousoperation, 可以submittingMutation
  • Module: 将Store分割成module, 便于management

5.2 PiniaIntroduction

Pinia is Vue 3 官方statusmanagementlibrary, 它providing了更简洁 API, 更 good TypeScriptsupport, 以及更flexible architecture.

Pinia 优势

  • 更简洁 API, 不需要Mutation
  • 更 good TypeScriptsupport
  • support many 个Store
  • supportComposition API
  • 体积更 small , performance更 good

提示

in after 续 课程in, 我们将详细介绍Vuex and Pinia using.

6. other通信方式

Vue还providing了一些other component间通信方式, 虽然不推荐 in produceenvironmentin频繁using, 但 in 某些specific场景 under 可能会 has 用.

6.1 $parent/$children

可以through$parent访问父componentinstance, through$children访问子componentinstancelist.

// 父component访问子component
this.$children[0].someMethod();

// 子component访问父component
this.$parent.someMethod();

warning

using$parent/$children会使component间耦合度增加, 不利于component 复用 and maintenance, 不推荐频繁using.

6.2 $refs

可以through$refs访问DOM元素 or componentinstance.

// 父component模板
<ChildComponent ref="childRef"></ChildComponent>

// 父component访问子componentinstance
this.$refs.childRef.someMethod();

7. component间通信 best practices

7.1 选择合适 通信方式

  • 父子component: 优先usingprops and event, or 者v-model
  • 兄弟component: 优先using父componentin转, or 者event总线, complex 场景usingVuex/Pinia
  • 跨级component: 优先usingProvide/Inject API, complex 场景usingVuex/Pinia
  • 任意component: 优先usingevent总线, complex 场景usingVuex/Pinia

7.2 避免过度耦合

  • 尽量reducingcomponent间 直接依赖
  • 避免using$parent/$children
  • complex statusmanagementusingVuex/Pinia

7.3 保持单向data流

尽量保持data流 单向性, 即父componentthroughprops向子component传递data, 子componentthroughevent向父component发送message, 避免直接modifyprops.

7.4 合理usingstatusmanagementlibrary

for 于 complex application, 合理usingVuex/Pinia可以简化component间通信, improvingcode 可maintenance性. 但 for 于 simple application, usingprops and event可能更加 high 效.

练习 1: 父子component通信

  1. creation一个父component and 一个子component.
  2. 父component向子component传递一个listdata.
  3. 子component显示该list, 并 for 每个list项添加一个delete按钮.
  4. 点击delete按钮时, 子component向父component发送event, 父componentdelete for 应 list项.

练习 2: event总线

  1. creation一个event总线.
  2. creation三个component: componentA, componentB and componentC.
  3. componentA发送event, componentB and componentC接收event并显示data.
  4. 确保 in component销毁时取消event监听.

练习 3: Provide/Inject API

  1. creation一个祖先component, providing一些data and method.
  2. creation一个父component, 嵌套 in 祖先componentin.
  3. creation一个子component, 嵌套 in 父componentin.
  4. 子componentusingInject API获取祖先componentproviding data and method, 并 in 页面 on 显示 and 调用.