共享事件通知:useImperativeHandle 与 useEventEmitter

1,808 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

如果你遇到一个十分复杂的页面,不同的组件不知道如何关联,那么你可能就要知道一个知识点:共享事件通知

在一个公共页面内,里面有很多模块,这时我们将这个页面划分多个组件,然后共同形成这个整体的页面,每个组件都是相互独立,但又有关联,而如何关联你需要使用 useImperativeHandleuseEventEmitter 来完成

使用场景以下面为例:

假设在一个页面内,我们有 A 和 B 两个组件,还有一个底层的按钮,在A组件中有个数字累加器的功能,触发按钮A会使累加器加1,点击按钮B和按钮C也会触发组件A的点击效果,使累加器加1。

注: 组件A 和 组件B 是兄弟关系,组件A 和 按钮C是父子关系

image.png

当我们想达到这个效果,就需要 useImperativeHandleuseEventEmitter 的帮助,接下来,将分别讲下这两个钩子的使用和区别

u=708488054,2121953317&fm=253&fmt=auto&app=138&f=GIF.webp

在线地址:Domesy/ahooks

useImperativeHandle

useImperativeHandle 是 React 官方提供的一个钩子,其作用是 可以让你在使用 ref 时自定义暴露给父组件的实例值。,

我们直接以这篇文章的例子来说明:还不懂 Hook?那你是真的Low了

先上代码:

import React, { useState, useImperativeHandle, useRef } from 'react';
  import { Button } from 'antd';

  const Children: React.FC<any> = ({cRef}) => {

    const [count, setCount] = useState<number>(0)

    const add = () => {
      setCount((c) => c + 1)
    }

    useImperativeHandle(cRef, () => ({
      add
    }))

    return <div style={{marginBottom: 20}}>
      <p>点击次数:{count}</p>
      <Button type='primary' onClick={() => add()}>加1</Button>
    </div>
  }


  const Mock: React.FC<any> = () => {
    const ref = useRef<any>(null)

    return (
      <div>
        <Children cRef={ref} />
        <Button type='primary' onClick={() => {
          ref.current.add()
        }}>父节点加1</Button>
      </div>
    );
  };

  export default Mock;

代码分析:

  1. 我们在父组件Mock传递一个ref给子组件Children
  2. 子组件通过传递的ref作为第一个参数传递给 useImperativeHandle,useImperativeHandle的第二各参数在把children的点击方法add暴露出去
  3. 最后父组件Mock在将暴露的方法 ref.current.add()使用即可,就能完成共享事件通知

useEventEmitter

useEventEmitter: 是 ahooks 提供的一个方法,适合的是在距离较远的组件之间进行事件通知,或是在多个组件之间共享事件通知(而非参数的共享)

先简单介绍下useEventEmitter的使用方法,在通过案例去讲解

如何使用: const click = useEventEmitter();

绑定事件(订阅事件): click.useSubscription(callback: (val: T) => void)=> void

调取事件(发送通知): click.emit(val: T) => void

image.png

代码示例

  import React, { useState, useRef } from 'react';
  import { Button } from 'antd';
  import { useEventEmitter } from 'ahooks';
  import { EventEmitter } from 'ahooks/lib/useEventEmitter';

  const Children: React.FC<{click: EventEmitter<number>}> = ({ click }) => {

    const [ count, setCount ] = useState<number>(0);
    const ref = useRef<any>(null)

    click.useSubscription((val) => {
      val === 1 ? message.info('父组件点击的') : message.info('兄弟节点点击的')
      ref.current.click();
    })

    const onClick = () => {
      setCount(v => v+1)
    }

    return <>
      <div>点击次数:{count}</div>
      <Button ref={ref} type='primary' style={{margin: '8px 0'}} onClick={onClick} >子组件点击</Button>
    </>
  }

  const Children1:  React.FC<{click: EventEmitter<number>}> = ({click}) => {
    return <Button style={{marginTop: 8}} type="primary" onClick={() => click.emit(2)}>兄弟节点点击</Button>
  }

  const Mock: React.FC<any> = () => {
    const click = useEventEmitter<number>();

    return (
      <>
        <Children click={click} />
        <div>
          <Button  type='primary' onClick={() => {
            click.emit(1)
          }} >父组件组件点击</Button>
        </div>
        <Children1 click={click} ></Children1>
      </>
    );
  };

  export default Mock;

分析代码:

  1. 首先在父组件 Mock 中,传递一个点击方法 click(useEventEmitter) 给子组件 Children
  2. 其次在子组件 Children 中通过传递的click进行绑定,使用 useSubscription 进行绑定,用Ref获取到按钮的点击方法,然后执行
  3. 然后通过 emit 进行消费,就能完成共享事件通知

useImperativeHandle 与 useEventEmitter 对比

从上述的案例中,我们发现 useImperativeHandle 是通关传递 ref 来作为参数,useEventEmitter通过订阅和派发来完成,两者其实差不多,但 useEventEmitter更加适用于距离较远的组件通知,或多个租件之间的共享通知

我记得useImperativeHandle多个组件之间不能共享, useEventEmitter相当于useImperativeHandle的增强版,以上只是本菜鸟的个人观点,如果不对,欢迎评论区讨论(● ̄(エ) ̄●)