前言

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就必须要有以下的基础功能:

  1. 向上发送事件(dispatch)
  2. 向下发送事件(broadcast)
  3. 只调用一次时间(once)
  4. 接收事件(on)
  5. 销毁事件(off)

    代码实

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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) { // 监听是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
    7
    function 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
    86
    import { 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
    6
    const { dispatch, on, broadcast } = useEmitter()
    on('update:modelValue', (v) => {
    emit('update:modelValue', v)
    dispatch('custom.value.change', v)
    })
    broadcast('fieldReset', 'reset')