vue3 - 非父子组件通信

291 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

provide/inject

Provide/Inject用于非父子组件之间共享数据

比如有一些深度嵌套的组件,子组件想要获取父组件的部分内 容

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

在这种情况下,我们就可以使用provide/inject

无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者

  • 父组件有一个 provide 选项来提供数据
  • 子组件有一个 inject 选项来开始使用这些数据

image.png

实际上,你可以将依赖注入看作是“long range props”

  • 父组件不需要知道哪些子组件使用它 provide 的 property
  • 子组件不需要知道 inject 的 property 来自哪里

数据提供者

<template>
    <div>
        <Child />
    </div>
</template>

<script>
import Child from './components/Child'

export default {
    components: {
        Child
    },
  
  data() {
    return {
        name: 'Klaus',
        age: 23
    }
  },

  // provide的值可以是一个对象,但是如果使用对象写法的时候
  // provide中提供的数据是不可以使用this的,此外此时对象中的this指向的是globalThis
  // 而vue在编译后会开启严格模式,所以对应的this会变为undefined
  /*
    provide: {
      name: 'Klaus',
      age: 23
    }
  */
  
  // provide在被vue内部调用的时候,会绑定正确的this
  // 所以可以在provide函数中使用this - 这也是provide最常用的写法
  provide() {
    return {
        name: this.name,
        age: this.age
    }
  }
}
</script>

数据使用者

<template>
    <div>
        <div>name: {{ name }}</div>
        <div>age: {{ age }}</div>
    </div>
</template>

<script>
export default {
  // 使用inject选项注入对应的数据
  inject: ['name', 'age']
}
</script>

此时存在一个问题,即provide中的name和age的值本质上是值拷贝,所以其对应的数据并不是响应式的

如果我们希望provide提供的数据是响应式的,我们可以使用computed api

provide提供者

<template>
    <div>
      <Child />
      <button @click="changeName">click me</button>
    </div>
</template>

<script>
import { computed } from 'vue'
import Child from './components/Child'

export default {
    components: {
        Child
    },

    data() {
        return {
            name: 'Klaus',
            age: 23
        }
    },

    provide() {
        return {
            // computed方法返回的是一个ref
            // 所以在注入对应的数据的时候,需要通过value属性去去对应的值
            // comoutedRef 并不会在mustache中自动解包,需要手动进行解包操作
            // 在vue@3.2中 可以配置app.config.unwrapInjectedRef = true选项
            // 来使ref在任何情况下,只要使用ref的值的时候,ref就会自动解析解包操作
            // 在vue@3.3中,该配置将默认开启
            name: computed(() => this.name),
            age: computed(() => this.age)
       }
},

    methods: {
        changeName() {
            this.name = 'Alex'
        }
    }
}
</script>

事件总线

provide/inject仅仅只是进行数据的传递,其必须存在祖先和孙辈关系

如果希望在兄弟组件之间进行数据通信或者在孙辈组件中触发事件给祖先组件

我们可以使用事件总线,例如mitt, tiny-emitter

utils/events.js

import mitt from 'mitt'

export default mitt()

事件发出组件

<template>
    <div>
        <button @click="handleClick">click me</button>
    </div>
</template>

<script>
import event from '/src/utils/events'

export default {
    methods: {
        handleClick() {
            event.emit('customEvent', {
                name: 'Klaus',
                age: 23
            })
        }
    }
}
</script>

事件监听组件

<template>
    <div>
        <Child />
    </div>
</template>

<script>
import event from '/src/utils/events'
import Child from './components/Child'

export default {
  components: { Child },

  // 在created生命周期中,就对需要监听的函数进行监听
  created() {
    event.on("customEvent", this.handleEvent);
  },

 // 在unmounted生命周期中,移除对应的事件监听
 // ps: 移除事件的时候,需要和添加对应事件为同一个引用,才可以被正确移除
 unmounted() {
    event.off('customEvent', this.handleEvent)
 },

 methods: {
    handleEvent({name, age}) {
       console.log(name, age)
    }
}
}
</script>