《巧用provide和inject完成精巧的tab组件》

306 阅读2分钟

目标:

希望用户如下使用代码:

<m-tabs>
		<m-tabs-nav>
			<m-tabs-item name="tab1"></m-tabs-item>
			<m-tabs-item name="tab2"></m-tabs-item>
		</m-tabs-nav>
		<m-tabs-content>
			<m-tabs-pane name="tab1"></m-tabs-pane>
			<m-tabs-pane name="tab2"></m-tabs-pane>
		</m-tabs-content>
</m-tabs>

接下来我们就来创建5个组件

思路分析:

爷爷组件tabs 有数据 selectedTag, 然后会向head和Body传值,然后,head会向自己的三个孩子传值,body会向自己的三个孩子传值

这里我们可以用到provide和inject

provide 选项允许我们指定我们想要提供给后代组件的数据/方法

然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property.

由于爷爷、爸爸、孙子三者之间的信息传递较为复杂,我们选择用eventbus来减少复杂

用new Vue()做EventBus

如果一个对象可以emit,emit, on, $off就是一个eventBus

Tab.vue
@Component
  export default class Tab extends Vue {
  ...
    eventBus = new Vue();

    @Provide('eventbus') eventbus: Vue = this.eventBus;
  }

爷爷provide, 然后儿子、孙子里inject

 @Component
  export default class TabHead extends Vue {
    @Inject() eventbus!: Vue;
    created(){
      console.log('爷爷给爸爸的eventBus')
			console.log(this.eventbus)
		}

  }
@Component
  export default class TabItem extends Vue {
    @Inject() eventbus!: Vue;
    @Prop(String) name: string | undefined;

    created() {
      console.log("爷爷给tab item的eventbus")
      console.log(this.eventBus)
      this.eventbus.$on('update:selected', (name: string) => {
        console.log(name);
      });
    }
  }

到底是谁在触发,谁在监听?

this.$emit 是当前实例在触发

this.eventBus.$emit 是eventBus这个对象本身在触发,注意两者的区别

在vue中,事件并不会冒泡,我们需要关注是在哪个对象上触发的事件

问题一:

<m-tabs @update:selected="yyy">
		<m-tabs-nav>
			<m-tabs-item name="tab1"></m-tabs-item>
			<m-tabs-item name="tab2"></m-tabs-item>
		</m-tabs-nav>
		<m-tabs-content>
			<m-tabs-pane name="tab1"></m-tabs-pane>
			<m-tabs-pane name="tab2"></m-tabs-pane>
		</m-tabs-content>
</m-tabs>

组件之间用eventBus触发和监听事件,那么爷爷上的@update:selected="yyy"事件会被触发吗?

答案是不会!因为eventBus触发的事件和这个组件没有关系

问题二:

<m-tabs @update:selected="yyy">
		<m-tabs-nav>
			<m-tabs-item name="tab1"></m-tabs-item>
			<m-tabs-item name="tab2"></m-tabs-item>
		</m-tabs-nav>
		<m-tabs-content>
			<m-tabs-pane name="tab1"></m-tabs-pane>
			<m-tabs-pane name="tab2"></m-tabs-pane>
		</m-tabs-content>
</m-tabs>

如果在上触发同名事件@update:selected,请问爷爷组件上的@update:selected事件会被触发吗?

答案是不会,在vue里事件是不会冒泡的

小结:

  1. 在哪个对象触发事件,就在那个对象上监听
  2. vue里事件不会冒泡

Props传值还是Data传值?

props: 需要用户传值

data不需要用户传值,自身维护

我们这里用data来传值

切换tag

实现切换tag功能有两步

第一步是从外界取值

点击一个tag后,爷爷组件会广而告之所有人,XXX被选中了,然后他的孙子们通过eventBus就能知道了!

然后我们根据传进来的变量,计算出css class, 然后更新样式

Tab组件(爷爷组件)

    @Provide('eventbus') eventbus: Vue = this.eventBus;

    mounted() {
      this.eventbus.$emit('update:selected', this.selected);
    }
TabPanelTabItem

created() {
      this.eventbus.$on('update:selected', (name: string) => {
        this.active = name === this.name;
      });
 }

孙子组件在created阶段就开始监听'update:selected'事件,然后判断被选中的是不是自己

第二步是用点击事件从内部更新

因为vue里事件不会冒泡,所以我们监听点击元素:tab-item, 设置onClick事件

TabItem

<template>
	<div class="tab-item" @click="xxx" :class="classes">
		<slot></slot>
	</div>
</template>

<script lang="ts">
  import Vue from 'vue';
  import {Component, Inject, Prop} from 'vue-property-decorator';

  @Component
  export default class TabItem extends Vue {
    @Inject() eventbus!: Vue;
    @Prop(String) name: string | undefined;
    @Prop(Boolean) disabled = false;
    active = false;

    get classes() {
      return {
        active: this.active
      };
    }

    created() {
      this.eventbus.$on('update:selected', (name: string) => {
        this.active = name === this.name;
      });
    }

    xxx() {
      this.eventbus.$emit('update:selected', this.name, this)
    }
  }

每次点击,就往事件中心广播 是哪个Name被点击了

本文为fjl的原创文章,著作权归本人和饥人谷所有,转载务必注明来源