Vue3组件化开发之非父子组件通信及插槽(十一)

1,173 阅读2分钟

没想到吧,我过年也在整理笔记。大家新年快乐,万事如心意!!!

1.非父子组件的通信

主要两种方式:

  1. Provide/Inject
  2. Mitt全局事件总线

1.1.Provide和Inject

一些深度嵌套的组件,下层组件要获得上层组件的一些信息

在这种情况下,如果我们仍然将props沿着组件链逐级传递下 去,就会非常的麻烦

对于这种情况下,我们可以使用 Provide 和 Inject

  • 上层组件有一个 provide 选项来给所有下层组件提供数据
  • 下层组件有一个 inject 选项来开始使用这些数据
  • 上层下层互相都不需要知道我给谁提供数据,我用了谁的数据
image-20220131223241168

我们基于这个结构演示使用

image-20220131223337064 image-20220131224213102

上面如果我们如果Provide中提供的一些数据是来自data,我们通过this来获取会报错哦

因为this指向会出问题,我们可以通过Provide函数返回对应数据给解决这个this指向之后数据又会丧失响应式

所以最终代码是:

image-20220201005255495

1.2.全局事件总线mitt库

Vue3从实例中移除了 onon、off 和 $once 方法

所以我们如果希望继续使用全局事件总线,要通过第三方的库

Vue3官方有推荐一些库,例如 mitttiny-emitter

我们来说mitt的使用

安装: npm install mitt

我们可以封装一个工具eventbus.js

image-20220201005716380

使用事件总线通信

image-20220201010710991

取消mitt事件监听

image-20220201010954589

2.插槽

让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

插槽也是为了让组件有更强的灵活和通用性,比如一个页面的导航都是左中右的布局结构,但是每个页面又会有所细小的区别,这时候我认为就需要考虑插槽了

分类:默认插槽、具名插槽、作用域插槽

2.1.插槽基本使用

有一个组件MySlotCpn.vue,该组件中有一个插槽,我们可以在插槽中放入需要显示的内容

image-20220201012441903

在使用到该组件地方,我们可以插入普通的内容、html元素、组件元素,这些元素写在组件的内部,最终会替换掉子组件的slot

image-20220201012654200

2.2.默认插槽

有时候我们在使用需要插槽的组件时没有传入对应的结构

我们可以默认配置一个没有插入内容时显示的内容

image-20220201013551807

多个插槽效果

image-20220201013858255

如果我们想对应不同内容插入不同插槽,可能就得具名插槽了

2.3.具名插槽

一个不带 name 的slot,会带有隐含的名字 default

image-20220201014816214

插槽取名使用name属性,插入对应的内容外层template加一个v-slot:对应的name名

image-20220201015033219

v-slot:的缩写是#,所以也可简写成#name名,例如#left

image-20220201015355095

2.4.动态插槽名

之前使用的插槽名都是固定的,比如#left,#center等

我们可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称,简写可以为#[dynamicSlotName]

image-20220201111113262

2.5.渲染作用域

父级模板里的所有内容都是在父级作用域中编译的

子模板里的所有内容都是在子作用域中编译的

image-20220201111751518

2.6.作用域插槽

我们希望插槽可以访问到子组件中的内容

我的理解是子向父传递数据,插槽来传递数据给使用的组件决定如何插入展示

1.我们在App.vue定义数据,通过props传递给ShowNames组件

image-20220201112843475

2.ShowNames组件中遍历names数据,定义插槽的prop

image-20220201113041016

3.通过v-slot:default或v-slot的方式获取到slot的props,使用slotProps中的item和index

image-20220201114519523

所以作用域插槽首先得我们传递一个数据过去,然后组件内通过插槽prop再回传给父组件,最后由父组件拿到数据插入结构后再放到对应的<slot>当中

如果只有默认插槽,我们就可以将 v-slot直接用在组件上

image-20220201114648922

如果出现多个插槽,要为插槽使用完整的template写法

image-20220201115408878

3.Vue2的全局事件总线和插槽

3.1.全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

3.2.消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx', this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

3.3.插槽

使用方式:

  1. 默认插槽:

    父组件中:
            <Category>
               <div>html结构1</div>
            </Category>
    子组件中:
            <template>
                <div>
                   <!-- 定义插槽 -->
                   <slot>插槽默认内容...</slot>
                </div>
            </template>
    
  2. 具名插槽:

    父组件中:
            <Category>
                <template slot="center">
                  <div>html结构1</div>
                </template>
    
                <template v-slot:footer>
                   <div>html结构2</div>
                </template>
            </Category>
    子组件中:
            <template>
                <div>
                   <!-- 定义插槽 -->
                   <slot name="center">插槽默认内容...</slot>
                   <slot name="footer">插槽默认内容...</slot>
                </div>
            </template>
    
  3. 作用域插槽:

    1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

    2. 具体编码:

      父组件中:
      		<Category>
      			<template scope="scopeData">
      				<!-- 生成的是ul列表 -->
      				<ul>
      					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
      				</ul>
      			</template>
      		</Category>
      
      		<Category>
      			<template slot-scope="scopeData">
      				<!-- 生成的是h4标题 -->
      				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
      			</template>
      		</Category>
      子组件中:
              <template>
                  <div>
                      <slot :games="games"></slot>
                  </div>
              </template>
      		
              <script>
                  export default {
                      name:'Category',
                      props:['title'],
                      //数据在子组件自身
                      data() {
                          return {
                              games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                          }
                      },
                  }
              </script>