diff --git a/packages/hooks/src/useEventEmitter/__tests__/index.test.ts b/packages/hooks/src/useEventEmitter/__tests__/index.test.ts index fedefffba5..a605b76026 100644 --- a/packages/hooks/src/useEventEmitter/__tests__/index.test.ts +++ b/packages/hooks/src/useEventEmitter/__tests__/index.test.ts @@ -13,6 +13,9 @@ describe('useEventEmitter', () => { event$.useSubscription((val) => { setCount((c) => c + val + 10); }); + event$.useSubscription(async (val) => { + setCount((c) => c + val + 10); + }, 'async'); return { event$, count, @@ -30,4 +33,16 @@ describe('useEventEmitter', () => { }); expect(hook.result.current.count).toBe(26); }); + + it('asyncEmit and subscribe should work', async () => { + const hook = setUp(); + await act(async () => { + await hook.result.current.event$.asyncEmit(1, 'async'); + }); + expect(hook.result.current.count).toBe(11); + await act(async () => { + await hook.result.current.event$.asyncEmit(2, 'async'); + }); + expect(hook.result.current.count).toBe(23); + }); }); diff --git a/packages/hooks/src/useEventEmitter/demo/demo2.tsx b/packages/hooks/src/useEventEmitter/demo/demo2.tsx new file mode 100644 index 0000000000..a6c5d9267a --- /dev/null +++ b/packages/hooks/src/useEventEmitter/demo/demo2.tsx @@ -0,0 +1,55 @@ +/** + * title: Invoke asynchronous methods arbitrarily across components + * desc: A component creates a 'getData' event. By calling 'getData' in component B, component A receives the notification + * + * title.zh-CN: 任意跨组件调用异步方法 + * desc.zh-CN: A组件创建了一个 `getData` 事件。在 B组件 中调用 `getData` ,A 组件就可以收到通知。 + */ + +import React, { useRef, FC, useState } from 'react'; +import { useEventEmitter } from 'ahooks'; + +const ComponentA: FC = function (props) { + const inputRef = useRef(); + const Bus = useEventEmitter(); + const getData = async (type: string) => { + // Your asynchronous operation + + return `${type} Get A Data`; + }; + Bus.useSubscription(getData, 'A/getData'); + + return ( + + ); +}; + +const ComponentB: FC = function (props) { + const Bus = useEventEmitter(); + const [data, setData] = useState('none'); + return ( +
+

{data}

+ +
+ ); +}; + +export default function () { + return ( + <> + + + + ); +} diff --git a/packages/hooks/src/useEventEmitter/index.en-US.md b/packages/hooks/src/useEventEmitter/index.en-US.md index cd8f239084..bda3a91a51 100644 --- a/packages/hooks/src/useEventEmitter/index.en-US.md +++ b/packages/hooks/src/useEventEmitter/index.en-US.md @@ -31,12 +31,45 @@ event$.useSubscription(val => { If you want to let the child component notify the parent component, you can just use `props` to pass a `onEvent` function. And if you want to let the parent component notify the child component, you can use `forwardRef` to retrieve the ref of child component. `useEventEmitter` is most suitable for event management among multiple components or between two components which are far away. +Call precisely by specifying the event name。 + +```js +event$.useSubscription(val => { + console.log(val); +},'console'); +event$.emit('hello','console') +``` + +Call the asynchronous method and get the result。 + +```js +event$.useSubscription(async (val) => { +return `async ${val}` +},'console'); +const res = await event$.asyncEmit('hello','console') +console.log(res[0]); +``` + +Support for incoming dependencies from subscription methods (keep the state value inside the subscription method up to date) + +```js +const [status,setStatus] = useState(''); +event$.useSubscription((val) => { + console.log("new status:",status) +},'console',[status]); +const res = event$.emit('hello','console') +``` + ## Examples ### Parent component shares a event +### Asynchronous events shared by different components (synchronously executed) + + + ## API ### Params @@ -49,5 +82,6 @@ const result: Result = useEventEmitter(); | Property | Description | Type | | --------------- | ----------------------------- | -------------------------------------- | -| emit | Emit a new event. | `(val: T) => void` | +| emit | Emit a new event. | `(val: T) => []:any` | +| asyncEmit | Send an asynchronous event | `(val: T, name?: string) => []:any` | | useSubscription | Subscribe to a event emitter. | `(callback: (val: T) => void) => void` | diff --git a/packages/hooks/src/useEventEmitter/index.ts b/packages/hooks/src/useEventEmitter/index.ts index 0b6a801b88..ec0a69da82 100644 --- a/packages/hooks/src/useEventEmitter/index.ts +++ b/packages/hooks/src/useEventEmitter/index.ts @@ -1,32 +1,46 @@ import { useRef, useEffect } from 'react'; +const DEFAULT_SUBSCRIPTION_NAME = 'useEventEmitter-Default-Name'; + type Subscription = (val: T) => void; export class EventEmitter { - private subscriptions = new Set>(); + // private subscriptions = new Set>(); + private subscriptions = new Object(); - emit = (val: T) => { - for (const subscription of this.subscriptions) { - subscription(val); + emit = (val: T, name?: string) => { + const subscriptions = this.subscriptions[name || DEFAULT_SUBSCRIPTION_NAME]; + const result: any[] = []; + if (subscriptions) { + subscriptions.forEach((s: Subscription) => { + result.push(s.call(this, val)); + }); } + return result; }; - useSubscription = (callback: Subscription) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const callbackRef = useRef>(); - callbackRef.current = callback; + asyncEmit = async (val: T, name?: string) => { + const subscriptions = this.subscriptions[name || DEFAULT_SUBSCRIPTION_NAME]; + const result: any[] = []; + if (subscriptions) { + for (let i = 0; i < subscriptions.length; i++) { + const s = subscriptions[i]; + result.push(await s.call(this, val)); + } + } + return result; + }; + useSubscription = (callback: Subscription, name?: string, deps?: any[]) => { // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { - function subscription(val: T) { - if (callbackRef.current) { - callbackRef.current(val); - } - } - this.subscriptions.add(subscription); + ( + this.subscriptions[name || DEFAULT_SUBSCRIPTION_NAME] || + (this.subscriptions[name || DEFAULT_SUBSCRIPTION_NAME] = []) + ).push(callback); return () => { - this.subscriptions.delete(subscription); + this.subscriptions[name || DEFAULT_SUBSCRIPTION_NAME] = null; }; - }, []); + }, deps || []); }; } diff --git a/packages/hooks/src/useEventEmitter/index.zh-CN.md b/packages/hooks/src/useEventEmitter/index.zh-CN.md index eb6eb6f4dd..973d4f0eee 100644 --- a/packages/hooks/src/useEventEmitter/index.zh-CN.md +++ b/packages/hooks/src/useEventEmitter/index.zh-CN.md @@ -31,12 +31,45 @@ event$.useSubscription(val => { 对于**子组件**通知**父组件**的情况,我们仍然推荐直接使用 `props` 传递一个 `onEvent` 函数。而对于**父组件**通知**子组件**的情况,可以使用 `forwardRef` 获取子组件的 ref ,再进行子组件的方法调用。 `useEventEmitter` 适合的是在**距离较远**的组件之间进行事件通知,或是在**多个**组件之间共享事件通知。 +通过指定事件名称精准调用。 + +```js +event$.useSubscription(val => { + console.log(val); +},'console'); +event$.emit('hello','console') +``` + +调用异步方法并获取结果。 + +```js +event$.useSubscription(async (val) => { +return `async ${val}` +},'console'); +const res = await event$.asyncEmit('hello','console') +console.log(res[0]); +``` + +支持订阅方法传入依赖项(保证订阅方法内部的state值为最新) + +```js +const [status,setStatus] = useState(''); +event$.useSubscription((val) => { + console.log("new status:",status) +},'console',[status]); +const res = event$.emit('hello','console') +``` + ## 代码演示 ### 父组件向子组件共享事件 +### 不同组件共享异步事件(可同步执行) + + + ## API ```typescript @@ -45,7 +78,8 @@ const result: Result = useEventEmitter(); ### Result -| 参数 | 说明 | 类型 | -| --------------- | ---------------- | -------------------------------------- | -| emit | 发送一个事件通知 | `(val: T) => void` | -| useSubscription | 订阅事件 | `(callback: (val: T) => void) => void` | +| 参数 | 说明 | 类型 | +| --------------- | -------------------- | -------------------------------------- | +| emit | 发送一个事件通知 | `(val: T) => []:any` | +| asyncEmit | 发送一个异步事件通知 | `(val: T, name?: string) => []:any` | +| useSubscription | 订阅事件 | `(callback: (val: T) => void) => void` |