涨姿势:使用Vue3需要知道的组件间通信方法
自我介绍
看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出吧。
前言
距离Vue3成为正式版本已经过去许久,前段时间更新的vue3.3
版本意味着vue3距离成熟又更进一步。因此看官们,vue3是时候拿出来用了。
Vue等现代前端框架核心就是组件化的开发思想,而组件化的开发范式,难免少不了组件间通信的运用。
下面就整理了一些vue3组件通信的常用套路,也许有你不知道的哦。
ps: 本文主要介绍组合式API的写法,如果你倾向于选项式API开发范式,请查阅官方文档
props
通过给子组件绑定数据
来通信
步骤:
- 父组件通过
v-bind
指令传递值(如果是jsx,则直接使用变量,函数) - 子组件通过
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>
小结
可以传递响应式数据
,也可以传递普通数据
,也可以传函数
(子将函数传给父组件,在父组件对应的事件处理函数
内调用时传值即可实现父传子
)
自定义事件
通过给子组件绑定事件来通信 步骤:
- 父组件通过
v-on
指令绑定事件名和处理函数 - 子组件通过
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>
所以在子组件当中需要defineProps
和defineEmits
来定义
<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>
打印出来是这样
可以看到父组件绑定的事件、变量、还有其他pros都在里面。
⚠️但要注意的是:未在defineEmits
,defineProps
里声明的才会在里面
这种一般用于组件库的二次封装,比方说我要封装一个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的方法,用来获取当前组件实例
还是用刚才的案例,在父组件上
子组件中
<script setup>
import { getCurrentInstance } from 'vue'
console.log(getCurrentInstance());
</script>
在上下文
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>
看到控制台已经有暴露的值了
模版里使用上下文$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>
看看控制台
这就是组件实例的ctx上下文
里的$parent
啊
provide和inject 依赖注入
provide()
接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
inject()
接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数可选的默认值。也可以是工厂函数
示例:
祖先组件当中
子组件当中
插槽
意思跟用法于vue2当中几乎一样,父传子直接在子组件当中插入,子传父则通过作用域插槽
。
它的使用引用一下官网的图
具名插槽就是利用指令 v-slot
简写为 #
,在子组件当中<slot name="具名插槽的名称"></slot>
这样去定义入口
然后在父组件当中
<template>
<MyComponent>
<template v-slot:具名插槽的名称>xxxx</template>
// 也可以用简写
<template #具名插槽的名称>xxxx</template>
</MyComponent>
</template>
结合官方文档的图
作用域插槽
其实就是在插槽入口通过<slot :msg="要传出的数据"></slot>
传入对应的值
// 子组件
<template>
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
</template>
// 父组件
<template>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
</template>
结合官方文档的图
当然这里也可以用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>
结尾
👏👏👏👏👏👏看完如果有所收获的话,欢迎👍点赞👍收藏👍以及评论区互动👍哦~
另外如有错误❌或者遗漏的,欢迎评论指正!
转载自:https://juejin.cn/post/7234028496085942333