美文网首页
Vue 3 组件之间如何通信

Vue 3 组件之间如何通信

作者: limengzhe | 来源:发表于2023-08-12 23:48 被阅读0次

    前言

    在一个 Vue 项目中,每一个 .vue 文件都可以被视为一个组件,组件之间可以相互嵌套,相互组合,这在实际开发中是非常常见的,比如以下结构:

    <template>
      <TheHeader />
      <main>
        <TheContent />
        <TheAside />
      </main>
    </template>
    

    其中,TheHeader、TheContent 以及 TheAside 都属于当前组件的子组件,他们三个同时又可以视为彼此的兄弟组件。

    那么组件与组件之间如何进行通信(传递数据,触发事件),以及都有哪些方式,下面我们就来一一介绍。

    由于 Vue 3 提供了两种书写风格,组合式 API 和 选项式 API,所以本文也会同时提供两种写法。

    父子组件通信

    props

    props 代表参数,子组件可以通过 props 属性设置和接收参数,父组件在调用时传递即可。

    选项式 API

    子组件:

    <script>
      export default {
        props: {
          greetingMessage: String,
        },
      }
    </script>
    

    父组件:

    <MyComponent greeting-message="hello" />
    

    组合式 API

    子组件:

    <script setup>
      const props = defineProps(["greetingMessage"])
    </script>
    

    父组件:

    <MyComponent greeting-message="hello" />
    

    emits

    子组件可以通过 emits 属性定义一些事件,在需要的时候触发,父组件通过 v-on@ 监听即可。

    选项式 API

    子组件:

    <script>
      export default {
        emits: ["check"],
    
        created() {
          this.$emit("check")
        },
      }
    </script>
    

    父组件:

    <MyComponent @check="onCehck" />
    

    组合式 API

    子组件:

    <script setup>
      const emits = defineEmits(["check"])
    
      emits("check")
    </script>
    

    父组件:

    <MyComponent @check="onCehck" />
    

    ref

    通过给子组件设置 ref 属性,父组件可以访问子组件并调用子组件暴露的方法。

    选项式 API

    选项式 API 默认会暴露所有属性和方法,无需额外的操作。

    子组件:

    <script>
      export default {
        methods: {
          check() {
            console.log("check")
          },
        },
      }
    </script>
    

    父组件:

    <MyComponent ref="MyComponent" />
    
    <script>
      export default {
        mounted() {
          this.$refs.MyComponent.check() // check
        },
      }
    </script>
    

    组合式 API

    组合式 API 需要通过 defineExpose 显式的暴露指定的方法,才能被其他组件访问。

    子组件:

    <script setup>
      function check() {
        console.log("check")
      }
    
      defineExpose({ check })
    </script>
    

    父组件:

    <MyComponent ref="MyComponent" />
    
    <script setup>
      import { ref, onMounted } from "vue"
    
      const MyComponent = ref()
    
      onMounted(() => {
        MyComponent.value.check() // check
      })
    </script>
    

    provide 和 inject

    provideinject 需要配合使用。父组件通过 provide 提供一个值,后代组件(包括子组件、孙组件……)通过 inject 获取这个值。该方法可以跨越层级使用。

    选项式 API

    父组件:

    <script>
      export default {
        provide: {
          foo: "123",
        },
      }
    </script>
    

    后代组件:

    <script>
      export default {
        inject: ["foo"],
        created() {
          console.log(this.foo) // 123
        },
      }
    </script>
    

    组合式 API

    父组件:

    <script setup>
      import { provide } from "vue"
    
      provide("foo", 123)
    </script>
    

    后代组件:

    <script setup>
      import { inject } from "vue"
    
      const foo = inject("foo")
    
      console.log(foo) // 123
    </script>
    

    非父子组件通信

    非父子组件由于不存在嵌套关系,所以一般不能使用以上四种方式,而是把数据或者方法存放在所有组件都能访问到的地方,比如状态管理,本地存储等。所以这些方法也同时可以在父子组件之间使用。

    状态管理 vuexpinia

    pinia 就是 vuex 的第五个版本,Vue 团队给他换了个马甲,为啥改名看这里。这里就以 pinia 为例。

    首先需要定义 store:

    // /src/stores/my-store.js
    import { defineStore } from "pinia"
    import { reactive } from "vue"
    
    export default defineStore("my-store-id", () => {
      const state = reactive({
        isShow: true,
      })
    
      return { state }
    })
    

    组件 A:

    <script setup>
      import useTransportStore from "@/stores/my-store"
    
      const { state } = useTransportStore()
    
      console.log(state.isShow) // true
    
      setTimeout(() => {
        state.isShow = false
      }, 2000)
    </script>
    

    组件 B:

    <script setup>
      import { watch } from "vue"
      import useTransportStore from "@/stores/my-store"
    
      const { state } = useTransportStore()
    
      console.log(state.isShow) // true
    
      watch(state.isShow, val => {
        console.log(val) // false
      })
    </script>
    

    由于在 store 中使用 reactive 进行了代理,所以即使任一组件对 state 进行修改,也都可以随时获取到最新的值,并且可以通过 watch 执行一些对应的操作。

    通过第三方库,比如 mitttiny-emitter

    在 Vue 3.x 中,EventBus 已经不再被支持,因为 $on(还有 $off$once)方法已经被移除了,为什么移除看这里。取而代之的是,Vue 官方推荐使用 mitt 和 tiny-emitter 进行通信。

    可能有人担心安装第三方库会不会增加打包体积,这里告诉大家,不用担心,mitt 压缩后只有 200 字节,极其特别的小。并且这两个包都不受框架限制。除了在 Vue 中使用,你还可以在 React 或者 nodejs 中使用。

    这里以 mitt 为例(mitt 翻译为手套,为啥叫手套咱也不知道,可能和 emit 比较接近?)。

    先安装一下:

    npm i mitt
    

    引入:

    // /src/utils/mitt.js
    import mitt from "mitt"
    
    const emitter = mitt()
    
    export default emitter
    

    组件 A:

    <script setup>
      import emitter from "@/utils/mitt"
    
      emitter.emit("check")
    </script>
    

    组件 B:

    <script setup>
      import emitter from "@/utils/mitt"
    
      emitter.on("check", () => {
        console.log("check")
      })
    </script>
    

    通过本地存储 localStoragesessionStorageCookies

    该方法不受框架限制,你可以在任意框架下使用该方法,和使用状态管理的原理类似,将数据存储在公共区域。区别是,本地存储的数据在组件中不能直接进行监听,需要写一些额外的方法。这里不做过多介绍。

    Vue 2 还可通过事件总线 EventBus

    上面已经提到,该方法在 Vue 3.x 已经不能直接使用了,推荐使用前面的几种方法。但这里还是写一下,算是回忆一下。

    导出一个 Vue 实例:

    // /src/utils/event-bus.js
    import Vue from "vue"
    export default new Vue()
    

    组件 A:

    import Bus from "event-bus.js"
    
    export default {
      methods: {
        handleClick(val) {
          Bus.$emit("functionName", val)
        },
      },
    }
    

    组件 B:

    import Bus from "event-bus.js"
    
    export default {
      created() {
        Bus.$on("functionName", val => {
          console.log(val)
        })
      },
    }
    

    总结

    总的来说,组件通信有以下几种方法:

    • 父子组件可以通过 propsemitsrefprovide/inject 等方法进行通信。

    • 非父子组件可以通过 pinia 、第三方库 mitt、本地存储等方法进行通信。

    在实际开发中请结合实际情况进行选择。

    希望以上内容对你有帮助,如果你还有其他方法,欢迎补充。


    参考文档

    相关文章

      网友评论

          本文标题:Vue 3 组件之间如何通信

          本文链接:https://www.haomeiwen.com/subject/rzlfmdtx.html