likes
comments
collection
share

涨姿势:使用Vue3需要知道的组件间通信方法

作者站长头像
站长
· 阅读数 12

自我介绍

看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出吧。

前言

距离Vue3成为正式版本已经过去许久,前段时间更新的vue3.3版本意味着vue3距离成熟又更进一步。因此看官们,vue3是时候拿出来用了。

Vue等现代前端框架核心就是组件化的开发思想,而组件化的开发范式,难免少不了组件间通信的运用。

下面就整理了一些vue3组件通信的常用套路,也许有你不知道的哦。

ps: 本文主要介绍组合式API的写法,如果你倾向于选项式API开发范式,请查阅官方文档

props

通过给子组件绑定数据来通信

步骤:

  1. 父组件通过v-bind指令传递值(如果是jsx,则直接使用变量,函数)
  2. 子组件通过defineProps编译宏来声明,如果想在script里使用需要获得编译宏的返回值 props

父组件

<script setup>

    // setup语法糖下,无须注册组件

    import Child from './Child.vue';

    // setup语法糖下,无需返回

    const msg = "父组件传递的值"

    const fn = (value)=>{
        // 接收来自子组件的内容
        console.log(value)

    }

</script>


<template>

    <p> 父组件</p>

    <br>

    <Child v-bind:msg="msg" :fn="fn"/>

</template>

子组件

<script setup>

    // 这里使用编译宏 defineProps ,入参可以是数组,可以是对象

    const props = defineProps(['msg','fn'])

    // 调用父组件传递过来的方法并传参数

    props.fn?.('子组件传递给父组件的内容')

</script>


<template>

<p>子组件</p>

    <!-- ⚠️模版里可以直接使用定义的变量,无须再返回或者props.msg -->
    <p>{{ msg }}</p>

</template>

小结

可以传递响应式数据,也可以传递普通数据,也可以传函数(子将函数传给父组件,在父组件对应的事件处理函数内调用时传值即可实现父传子

自定义事件

通过给子组件绑定事件来通信 步骤:

  1. 父组件通过v-on指令绑定事件名和处理函数
  2. 子组件通过defineEmits编译宏来声明

父组件

<template>
    <Child @some-event="callback" />
</template>

子组件

<script setup>
    const emits = defineEmits(['someEvent'])
    // 传递数据给父组件
    emits('someEvent','子组件传回去的数据')
</script>

小结

可以传递响应式数据,也可以传递普通数据,也可以传函数(父组件将函数传给子组件,子组件内调用时传值即可实现子传父

ps: 函数作为参数代表了可以无限套娃,可以通过函数的入参来传递数据,这也正是函数式编程的核心。

v-model

别忘了v-model语法糖,使用上和原理上基本跟vue2没啥区别

在父组件当中给子组件这样设置

<template>
    <Child v-model="msg" />
</template>

其实等效于这样,绑定一个modelValue的属性和update:modelValue的自定义事件

<template>
    <Child

    :modelValue="msg"

    @update:modelValue="newValue => msg = newValue"

    />
</template>

所以在子组件当中需要definePropsdefineEmits来定义

<script setup>
    defineProps(['modelValue'])

    const emits = defineEmits(['update:modelValue'])
    
    // 传递数据给父组件
    emits('update:modelValue','子组件传回去的数据')
</script>

跟vue2有区别的地方,可以支持多个v-model了

在vue3当中引入了v-model参数的概念,什么意思呢,就是v-model:[argument],它可以这样去使用

<template>
    
    <Child v-model:title="msg" />
    
    </template>

相当于

<template>
    <Child

    :title="msg"

    @update:title="newValue => msg = newValue"

    />
</template>

看到这里,子组件怎么写不是一目了然啰

<script setup>
    defineProps(['title'])
    const emits = defineEmits(['update:title'])
</script>

有了这个概念,对多个v-model的绑定使用也就轻轻松松了

<template>
   
   // 多个v-model
   <Child3 v-model="msg" v-model:title="msg" />
</template>

值得一提的是:3.3版本当中有了defineModel这个编译宏,有兴趣可以了解一下

useAttrs

组合式API的方法,用来获取当前属性,在setup语法糖当中使用

这个其实就相当于vue2 当中的$attrs$listeners的结合

比如这样,给子组件绑定了很多东西

<template>
   <Child 
   v-model:title="title" 
   v-on:some-event="callback"
   :msg="msg" 
   data-name="JetTsang"
   />
</template>

在子组件里

<script setup>
    import { useAttrs } from 'vue')
    console.log(useAttrs())
</script>

打印出来是这样

涨姿势:使用Vue3需要知道的组件间通信方法

可以看到父组件绑定的事件、变量、还有其他pros都在里面。

⚠️但要注意的是:未在defineEmitsdefineProps里声明的才会在里面

这种一般用于组件库的二次封装,比方说我要封装一个input,需要原封不动将属性传给el-input,可以这样写

// MyButton.vue 
<script setup>
import { useAttrs } from 'vue')
const attrs = useAttrs()
</script>
<template>
   // 用简写
   <el-input :="attrs" />
   
   // 或者 v-bind
   <el-input v-bind="attrs" />
</template>

// 父组件上使用
<script setup>
import { ref } from 'vue')
const input = ref('')
</script>
<template>
   <MyButton v-model="input" placeholder="Please input" />
</template>

实例操作

getCurrentInstance

组合式API的方法,用来获取当前组件实例

还是用刚才的案例,在父组件上

涨姿势:使用Vue3需要知道的组件间通信方法 子组件中

<script setup>
    import { getCurrentInstance } from 'vue'
    console.log(getCurrentInstance());
</script>

涨姿势:使用Vue3需要知道的组件间通信方法 在上下文ctx当中,可以看到几个熟悉的属性,通过组件实例可以获取到,类似于dom操作,通过parent获取父级别等等,除了通信其实可以做很多事情

ref 模板引用

这里的ref是指的给子组件加上ref这个属性,类似于vue2当中的this.$refs.child

父组件当中通过ref获取组件实例,然后子组件通过defineExpose暴露出来的方法或者属性,父组件就可以通过ref访问了

// 父组件中
<script setup>
    import { ref } from 'vue'
    // 声明一个 ref 来存放该元素的引用
    // 必须和模板里的 ref 同名
    const child = ref(null)
    onMounted(()=>{
        console.log(child);
    })
</script>
<template>
    <Child ref="child"/>
</template>

// 子组件中
<script setup>
    const someChildData = '子组件的data'
    defineExpose({
        someChildData
    })
</script>

看到控制台已经有暴露的值了 涨姿势:使用Vue3需要知道的组件间通信方法

模版里使用上下文$parent

通常在模版里,能使用到上下文的几个属性,这里如果用于通信的话,那就使用$parent

// 父组件中
<script setup>
    const message = 'defineExpose暴露出来的信息'
    // 定义暴露到子组件上下文的$parent当中
    defineExpose({
        message
    })
</script>

// 子组件
<template>
// 模版语法当中使用$parent
<button @click="handleCLick($parent)">

// 如果想保留事件对象也可以
<button @click="(e)=>handleCLick($parent,e)">

{{ $parent }}
</button>
</template>
<script setup>
    const handleCLick = ($parent)=>{
        console.log($parent)
    }
</script>

看看控制台

涨姿势:使用Vue3需要知道的组件间通信方法

这就是组件实例的ctx上下文里的$parent涨姿势:使用Vue3需要知道的组件间通信方法

provide和inject 依赖注入

provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

涨姿势:使用Vue3需要知道的组件间通信方法 inject() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数可选的默认值。也可以是工厂函数

涨姿势:使用Vue3需要知道的组件间通信方法

示例:

祖先组件当中

涨姿势:使用Vue3需要知道的组件间通信方法

子组件当中 涨姿势:使用Vue3需要知道的组件间通信方法

插槽

意思跟用法于vue2当中几乎一样,父传子直接在子组件当中插入,子传父则通过作用域插槽

它的使用引用一下官网的图 涨姿势:使用Vue3需要知道的组件间通信方法

具名插槽就是利用指令 v-slot 简写为 #,在子组件当中<slot name="具名插槽的名称"></slot>这样去定义入口

然后在父组件当中

<template>
    <MyComponent>
        <template v-slot:具名插槽的名称>xxxx</template> 
        // 也可以用简写
        <template #具名插槽的名称>xxxx</template> 
    </MyComponent>
</template>

结合官方文档的图

涨姿势:使用Vue3需要知道的组件间通信方法

作用域插槽

其实就是在插槽入口通过<slot :msg="要传出的数据"></slot>传入对应的值

// 子组件
<template>
    <div> 
        <slot :text="greetingMessage" :count="1"></slot> 
    </div>
</template>

// 父组件
<template>
<MyComponent v-slot="slotProps"> 
{{ slotProps.text }} {{ slotProps.count }} 
</MyComponent>
</template>

结合官方文档的图

涨姿势:使用Vue3需要知道的组件间通信方法

当然这里也可以用slot这里也可以v-bind直接绑定值,那就是整个slotProps

或者结合具名插槽 #插槽名称="slotProps(传递的数据包进这个对象里)"

全局事件总线

是的,虽然vue3的组合式api没有了this/vm等概念,但其实也不妨碍全局事件总线的使用。

全局事件总线其实就是发布订阅的思想,那么其实通过一个事件发布订阅的库来实现即可。

通常在vue3当中,都会用轻量级发布订阅库mitt

简单的使用

安装

npm install --save mitt | pnpm add mitt

使用

// 在对应的路径下定义
// eventBus/mitt.js 

import mitt from 'mitt'

export default mitt()


// 组件当中使用
<script setup>
    import emitter from '@/eventBus/mitt'

    // listen to an event
    emitter.on('foo', e => console.log('foo', e) )

    // listen to all events
    emitter.on('*', (type, e) => console.log(type, e) )

    // fire an event 可以在其他组件当中emit即可
    emitter.emit('foo', { a: 'b' })

    // clearing all events
    emitter.all.clear()

    // working with handler references:
    function onFoo() {}
    emitter.on('foo', onFoo)   // listen
    emitter.off('foo', onFoo)  // unlisten
</script>

小结

可以传递响应式数据,也可以传递普通数据,可以传函数(但没必要搞这么复杂,且可维护性差)

全局状态管理

全局状态管理当然能实现组件间的通信,在vue3时代,它的最佳搭档从vuex 换成pinia了,这边就只介绍pinia的组合式使用了。

pinia

pinia因为对组合式API和typescript的支持比较好,符合vue3的设计思想,可以有机的结合。

简单使用⬇️

// 在对应的路径下定义
// stores/counter.js 

import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', 
() => { 
    const count = ref(0) 
    function increment() {
        count.value++ 
    } 
    return { count, increment } 
})


// 组件当中使用
<script setup>
    import { useCounterStore } from '@/stores/counter'
    const counter = useCounterStore()
    counter.count++
    // 使用 action
    counter.increment()
</script>
<template>
  <!-- 直接从 store 中访问 state -->
  <div>Current Count: {{ counter.count }}</div>
</template>

结尾

👏👏👏👏👏👏看完如果有所收获的话,欢迎👍点赞👍收藏👍以及评论区互动👍哦~

另外如有错误❌或者遗漏的,欢迎评论指正!