前言
vue3新特性,hooks钩子函数,用过react hooks的应该很熟悉,思想是一致的,学习成本也不高,跟普通的函数相比,最大的特点就是能再函数中使用组件/页面的一些生命周期了,这时你应该会马上想到vue2的mixin函数,但是hooks会比mixin更加灵活,且副作用更小。
vue3组件传值除了emit和props,推荐provide/injeect,store。跨组件传值已经不能像之前一样new Vue(),因为实例上的$on
、$off
和 $once
实例方法被删除了。所以我找了他的替代方案:miit
: Tiny 200b functional event emitter / pubsub.
即小型功能事件发射接收,类似vue2版本中的$emit
、$on
1 | npm install --save mitt |
在组件层级复杂的情况下,这非常有必要,且个人认为,使用频率较高,该需求出险频率非常高
附上链接:https://www.npmjs.com/package/mitt
前置知识点
getCurrentInstance
: 获取当前组件的实例,顺便提一句,vue3不支持操作prototype一样去挂载全局方法,建议使用getCurrentInstance().appContext.config.globalProperties
访问全局属性getCurrentInstance
只能在 setup 或生命周期钩子中调用。
需求
在组件开发中,不仅有子组件需要调用父组件的场景,也有场景是相反的,父组件需要调用子组件的方案,vue3+ts
如果你直接ref.xxx访问子组件的方法或报错,因为它并不知道你的子组件中有什么方法,有人直接把整个子组件$emit给父组件,然后调用他的属性,不推荐此方案,不考虑性能,我觉得很繁琐。那么,我们实现的emitter就必须要有以下的基础功能:
- 向上发送事件(dispatch)
- 向下发送事件(broadcast)
- 只调用一次时间(once)
- 接收事件(on)
- 销毁事件(off)
代码实
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function on(type, handler) {
const handleWrapper = (e) => {
const { value, type, emitComponentInstance } = e
if (type === BROADCAST) { // 监听是否是广播事件
if (isChildComponent(currentComponentInstance, emitComponentInstance)) { // 广播即当前接收的组件是子组件时,因为广播是从向往下
handler && handler(...value)
}
} else if (type === DISPATCH) { // 监听是dispatch,当前接收的组件是父组件
if (isChildComponent(emitComponentInstance, currentComponentInstance)) { // 判断当前组件是否是发送事件组件的子组件的判断逻辑
handler && handler(...value)
}
} else {
handler && handler(...value)
}
}
// 把接收到的handler保存起来,因为需要off掉,注意这个handler必要保持唯一 ,function是引用类型,传递给off的时候,必须找到相同的地址才行。这里的`wrapper`变量可以使用`Symbol`类型
handler[wrapper] = handleWrapper
emitter.on(type, handleWrapper)
}1
2
3
4
5
6
7function broadcast(type, ...args) {
emitter.emit(type, {
type: BROADCAST, // 事件类型,区分第一个参数,第一个参数是事件名称
emitComponentInstance: currentComponentInstance, // 当前组件实例
value: args
})
}1
2
3
4
5
6
7
8//逻辑同上,时间类型不同
function dispatch(type, ...args) {
emitter.emit(type, {
type: DISPATCH,
emitComponentInstance: currentComponentInstance,
value: args
})
}1
2
3
4// off掉的是唯一相同的handler!
function off(type, handler) {
emitter.off(type, handler[wrapper])
}1
2
3
4
5
6
7
8// 执行一次的原理就是调用后即off掉
function once(type, handler) {
const handleOn = (...args) => {
handler && handler(...args)
off(type, handleOn)
}
on(type, handleOn)
}完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86import { getCurrentInstance } from 'vue'
import mitt from 'mitt'
const DISPATCH = 'dispatch'
const BROADCAST = 'broadcast'
const wrapper = Symbol('wrapper')
const emitter = mitt()
export function useEmitter() {
const currentComponentInstance = getCurrentInstance()
function on(type, handler) {
const handleWrapper = (e) => {
const { value, type, emitComponentInstance } = e
if (type === BROADCAST) {
if (isChildComponent(currentComponentInstance, emitComponentInstance)) {
handler && handler(...value)
}
} else if (type === DISPATCH) {
if (isChildComponent(emitComponentInstance, currentComponentInstance)) {
handler && handler(...value)
}
} else {
handler && handler(...value)
}
}
// Save the real handler because the need to call off
handler[wrapper] = handleWrapper
emitter.on(type, handleWrapper)
}
function broadcast(type, ...args) {
emitter.emit(type, {
type: BROADCAST,
emitComponentInstance: currentComponentInstance,
value: args
})
}
function dispatch(type, ...args) {
emitter.emit(type, {
type: DISPATCH,
emitComponentInstance: currentComponentInstance,
value: args
})
}
function off(type, handler) {
emitter.off(type, handler[wrapper])
}
function once(type, handler) {
const handleOn = (...args) => {
handler && handler(...args)
off(type, handleOn)
}
on(type, handleOn)
}
return {
on,
broadcast,
dispatch,
off,
once
}
}
/**
* check componentChild is componentParent child components
* @param {*} componentChild
* @param {*} componentParent
*/
function isChildComponent(componentChild, componentParent) {
const parentUId = componentParent.uid
while (componentChild && componentChild?.parent?.uid !== parentUId) {
componentChild = componentChild.parent
}
return Boolean(componentChild)
}基本使用
1
2
3
4
5
6const { dispatch, on, broadcast } = useEmitter()
on('update:modelValue', (v) => {
emit('update:modelValue', v)
dispatch('custom.value.change', v)
})
broadcast('fieldReset', 'reset')
- 本文链接:https://cong1223.github.io/2021/01/04/vue3-%E8%87%AA%E5%AE%9A%E4%B9%89hook%E4%B9%8BuseEmitter/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub IssuesGitHub Discussions