From 0b50ba3af39f673cf60a884fd35f370bb04898fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=A3=E5=8D=9A=E6=96=87?= <1030698842@qq.com> Date: Thu, 25 Apr 2024 17:51:59 +0800 Subject: [PATCH 1/2] feat(useEventBus): support-new-way-publish-subscribe --- .../hooks/src/Bus/__tests__/index.test.ts | 14 ++++++ packages/hooks/src/Bus/index.ts | 47 +++++++++++++++++++ .../src/useEventBus/__tests__/index.test.ts | 20 ++++++++ packages/hooks/src/useEventBus/demo/demo1.tsx | 39 +++++++++++++++ packages/hooks/src/useEventBus/index.en-US.md | 19 ++++++++ packages/hooks/src/useEventBus/index.tsx | 15 ++++++ packages/hooks/src/useEventBus/index.zh-CN.md | 21 +++++++++ 7 files changed, 175 insertions(+) create mode 100644 packages/hooks/src/Bus/__tests__/index.test.ts create mode 100644 packages/hooks/src/Bus/index.ts create mode 100644 packages/hooks/src/useEventBus/__tests__/index.test.ts create mode 100644 packages/hooks/src/useEventBus/demo/demo1.tsx create mode 100644 packages/hooks/src/useEventBus/index.en-US.md create mode 100644 packages/hooks/src/useEventBus/index.tsx create mode 100644 packages/hooks/src/useEventBus/index.zh-CN.md diff --git a/packages/hooks/src/Bus/__tests__/index.test.ts b/packages/hooks/src/Bus/__tests__/index.test.ts new file mode 100644 index 0000000000..a47abf9dbf --- /dev/null +++ b/packages/hooks/src/Bus/__tests__/index.test.ts @@ -0,0 +1,14 @@ +import { Bus } from '../index'; + +describe('Bus', () => { + it('should work for useEffect', () => { + let mountedState = 1; + //订阅 + Bus.$on('updateMountedState', (args) => { + mountedState = args; + }); + //发布 + Bus.$emit('updateMountedState', 2); + expect(mountedState).toBe(2); + }); +}); diff --git a/packages/hooks/src/Bus/index.ts b/packages/hooks/src/Bus/index.ts new file mode 100644 index 0000000000..af38acc20f --- /dev/null +++ b/packages/hooks/src/Bus/index.ts @@ -0,0 +1,47 @@ +interface TypeEventBus { + callbacks: any; + $off: (name: string) => void; + $emit: (name: string, ...args: any[]) => any; + $asyncEmit: (name: string, ...args: any[]) => any; + $on: (name: string, fn: any) => void; +} + +export const Bus: TypeEventBus = { + callbacks: {}, + + // 解除监听 用的比较少 + $off(name) { + this.callbacks[name] = null; + }, + + // 提交通信封装 + $emit(name, ...args) { + const cbs = this.callbacks[name]; + let result = undefined; + if (cbs) { + cbs.forEach((c: any) => { + result = c.call(this, ...args); + }); + } + return result; + }, + + async $asyncEmit(name, ...args) { + const cbs = this.callbacks[name]; + let result = undefined; + if (cbs) { + for (let i = 0; i < cbs.length; i++) { + const c = cbs[i]; + result = await c.call(this, ...args); + } + } + return result; + }, + + // 监听封装 + $on(name, fn) { + (this.callbacks[name] || (this.callbacks[name] = [])).push(fn); + }, +}; + +export default Bus; diff --git a/packages/hooks/src/useEventBus/__tests__/index.test.ts b/packages/hooks/src/useEventBus/__tests__/index.test.ts new file mode 100644 index 0000000000..403ecd7bc3 --- /dev/null +++ b/packages/hooks/src/useEventBus/__tests__/index.test.ts @@ -0,0 +1,20 @@ +import { renderHook } from '@testing-library/react'; +import useEventBus from '../index'; +import Bus from '../../Bus'; + +describe('useEventBus', () => { + it('test in component', async () => { + let mountedState = 1; + const hook = renderHook(() => { + const fc = (args) => { + mountedState = args; + }; + useEventBus('订阅方法名称', fc, []); + //发布 + Bus.$emit('订阅方法名称', 2); + }); + expect(mountedState).toBe(1); + hook.rerender(); + expect(mountedState).toBe(2); + }); +}); diff --git a/packages/hooks/src/useEventBus/demo/demo1.tsx b/packages/hooks/src/useEventBus/demo/demo1.tsx new file mode 100644 index 0000000000..118202fcb9 --- /dev/null +++ b/packages/hooks/src/useEventBus/demo/demo1.tsx @@ -0,0 +1,39 @@ +/** + * title: Basic usage + * desc: `useEventBus` Be synonymous with `Bus.$on`,However, you can depend on the value of the state and remount the function when dependent on updates,Once subscribed, methods can be called from anywhere + * + * title.zh-CN: 基础用法 + * desc.zh-CN: `useEventBus` 用法等同于 `Bus.$on`,但是可以依赖状态的值,依赖更新时重新挂载函数。订阅后任何地方都可以调用方法 + */ + +import React, { useState } from 'react'; +import { Bus, useEventBus } from 'ahooks'; + +export default () => { + const [count, setCount] = useState(0); + + const updateCount = async () => { + setCount(count + 1); + }; + // 订阅后任意地方都可以调用,适合跨组件,跨组件内外. 注意:组件卸载后会销毁 + useEventBus('updateCount', updateCount, [count]); + + return ( +
+

Count: {count}

+

+ +

+
+ ); +}; diff --git a/packages/hooks/src/useEventBus/index.en-US.md b/packages/hooks/src/useEventBus/index.en-US.md new file mode 100644 index 0000000000..a8664f1904 --- /dev/null +++ b/packages/hooks/src/useEventBus/index.en-US.md @@ -0,0 +1,19 @@ +--- +nav: + path: /hooks +--- + +# useEventBus + +`useEventBus` Be synonymous with `Bus.$on`,However, you can depend on the value of the state and remount the function when dependent on updates,Once subscribed, methods can be called from anywhere + +### Basic usage + + + +## API + +```typescript +// hooks 订阅 +useEventBus('Subscription method name',Subscribe to callback methods,[dependency]); +``` diff --git a/packages/hooks/src/useEventBus/index.tsx b/packages/hooks/src/useEventBus/index.tsx new file mode 100644 index 0000000000..694ad79bf4 --- /dev/null +++ b/packages/hooks/src/useEventBus/index.tsx @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; +import Bus from '../Bus'; + +export const useEventBus = (callbackName: string, callback: Function, deps?: any[]) => { + useEffect(() => { + Bus.$on(callbackName, callback); + return () => { + Bus.$off(callbackName); + }; + }, deps || []); + + return []; +}; + +export default useEventBus; diff --git a/packages/hooks/src/useEventBus/index.zh-CN.md b/packages/hooks/src/useEventBus/index.zh-CN.md new file mode 100644 index 0000000000..7f85ef0697 --- /dev/null +++ b/packages/hooks/src/useEventBus/index.zh-CN.md @@ -0,0 +1,21 @@ +--- +nav: + path: /hooks +--- + +# useEventBus + +`useEventBus` 用法等同于 `Bus.$on`,但是可以依赖状态的值,依赖更新时重新挂载函数。订阅后任何地方都可以调用方法 + +## 代码演示 + +### 基础用法 + + + +## API + +```typescript +// hooks 订阅 +useEventBus('订阅方法名称',订阅回调方法,[依赖项]); +``` From 71bd837f3a41b02fdb758d32a0f8ba5ff3ff0a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=A3=E5=8D=9A=E6=96=87?= <1030698842@qq.com> Date: Mon, 1 Jul 2024 19:22:55 +0800 Subject: [PATCH 2/2] refactor/useEventEmitter --- .../hooks/src/Bus/__tests__/index.test.ts | 14 ----- packages/hooks/src/Bus/index.ts | 47 ---------------- .../src/useEventBus/__tests__/index.test.ts | 20 ------- packages/hooks/src/useEventBus/demo/demo1.tsx | 39 ------------- packages/hooks/src/useEventBus/index.en-US.md | 19 ------- packages/hooks/src/useEventBus/index.tsx | 15 ----- packages/hooks/src/useEventBus/index.zh-CN.md | 21 ------- .../useEventEmitter/__tests__/index.test.ts | 15 +++++ .../hooks/src/useEventEmitter/demo/demo2.tsx | 55 +++++++++++++++++++ .../hooks/src/useEventEmitter/index.en-US.md | 36 +++++++++++- packages/hooks/src/useEventEmitter/index.ts | 46 ++++++++++------ .../hooks/src/useEventEmitter/index.zh-CN.md | 42 ++++++++++++-- 12 files changed, 173 insertions(+), 196 deletions(-) delete mode 100644 packages/hooks/src/Bus/__tests__/index.test.ts delete mode 100644 packages/hooks/src/Bus/index.ts delete mode 100644 packages/hooks/src/useEventBus/__tests__/index.test.ts delete mode 100644 packages/hooks/src/useEventBus/demo/demo1.tsx delete mode 100644 packages/hooks/src/useEventBus/index.en-US.md delete mode 100644 packages/hooks/src/useEventBus/index.tsx delete mode 100644 packages/hooks/src/useEventBus/index.zh-CN.md create mode 100644 packages/hooks/src/useEventEmitter/demo/demo2.tsx diff --git a/packages/hooks/src/Bus/__tests__/index.test.ts b/packages/hooks/src/Bus/__tests__/index.test.ts deleted file mode 100644 index a47abf9dbf..0000000000 --- a/packages/hooks/src/Bus/__tests__/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Bus } from '../index'; - -describe('Bus', () => { - it('should work for useEffect', () => { - let mountedState = 1; - //订阅 - Bus.$on('updateMountedState', (args) => { - mountedState = args; - }); - //发布 - Bus.$emit('updateMountedState', 2); - expect(mountedState).toBe(2); - }); -}); diff --git a/packages/hooks/src/Bus/index.ts b/packages/hooks/src/Bus/index.ts deleted file mode 100644 index af38acc20f..0000000000 --- a/packages/hooks/src/Bus/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -interface TypeEventBus { - callbacks: any; - $off: (name: string) => void; - $emit: (name: string, ...args: any[]) => any; - $asyncEmit: (name: string, ...args: any[]) => any; - $on: (name: string, fn: any) => void; -} - -export const Bus: TypeEventBus = { - callbacks: {}, - - // 解除监听 用的比较少 - $off(name) { - this.callbacks[name] = null; - }, - - // 提交通信封装 - $emit(name, ...args) { - const cbs = this.callbacks[name]; - let result = undefined; - if (cbs) { - cbs.forEach((c: any) => { - result = c.call(this, ...args); - }); - } - return result; - }, - - async $asyncEmit(name, ...args) { - const cbs = this.callbacks[name]; - let result = undefined; - if (cbs) { - for (let i = 0; i < cbs.length; i++) { - const c = cbs[i]; - result = await c.call(this, ...args); - } - } - return result; - }, - - // 监听封装 - $on(name, fn) { - (this.callbacks[name] || (this.callbacks[name] = [])).push(fn); - }, -}; - -export default Bus; diff --git a/packages/hooks/src/useEventBus/__tests__/index.test.ts b/packages/hooks/src/useEventBus/__tests__/index.test.ts deleted file mode 100644 index 403ecd7bc3..0000000000 --- a/packages/hooks/src/useEventBus/__tests__/index.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import useEventBus from '../index'; -import Bus from '../../Bus'; - -describe('useEventBus', () => { - it('test in component', async () => { - let mountedState = 1; - const hook = renderHook(() => { - const fc = (args) => { - mountedState = args; - }; - useEventBus('订阅方法名称', fc, []); - //发布 - Bus.$emit('订阅方法名称', 2); - }); - expect(mountedState).toBe(1); - hook.rerender(); - expect(mountedState).toBe(2); - }); -}); diff --git a/packages/hooks/src/useEventBus/demo/demo1.tsx b/packages/hooks/src/useEventBus/demo/demo1.tsx deleted file mode 100644 index 118202fcb9..0000000000 --- a/packages/hooks/src/useEventBus/demo/demo1.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * title: Basic usage - * desc: `useEventBus` Be synonymous with `Bus.$on`,However, you can depend on the value of the state and remount the function when dependent on updates,Once subscribed, methods can be called from anywhere - * - * title.zh-CN: 基础用法 - * desc.zh-CN: `useEventBus` 用法等同于 `Bus.$on`,但是可以依赖状态的值,依赖更新时重新挂载函数。订阅后任何地方都可以调用方法 - */ - -import React, { useState } from 'react'; -import { Bus, useEventBus } from 'ahooks'; - -export default () => { - const [count, setCount] = useState(0); - - const updateCount = async () => { - setCount(count + 1); - }; - // 订阅后任意地方都可以调用,适合跨组件,跨组件内外. 注意:组件卸载后会销毁 - useEventBus('updateCount', updateCount, [count]); - - return ( -
-

Count: {count}

-

- -

-
- ); -}; diff --git a/packages/hooks/src/useEventBus/index.en-US.md b/packages/hooks/src/useEventBus/index.en-US.md deleted file mode 100644 index a8664f1904..0000000000 --- a/packages/hooks/src/useEventBus/index.en-US.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -nav: - path: /hooks ---- - -# useEventBus - -`useEventBus` Be synonymous with `Bus.$on`,However, you can depend on the value of the state and remount the function when dependent on updates,Once subscribed, methods can be called from anywhere - -### Basic usage - - - -## API - -```typescript -// hooks 订阅 -useEventBus('Subscription method name',Subscribe to callback methods,[dependency]); -``` diff --git a/packages/hooks/src/useEventBus/index.tsx b/packages/hooks/src/useEventBus/index.tsx deleted file mode 100644 index 694ad79bf4..0000000000 --- a/packages/hooks/src/useEventBus/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useEffect } from 'react'; -import Bus from '../Bus'; - -export const useEventBus = (callbackName: string, callback: Function, deps?: any[]) => { - useEffect(() => { - Bus.$on(callbackName, callback); - return () => { - Bus.$off(callbackName); - }; - }, deps || []); - - return []; -}; - -export default useEventBus; diff --git a/packages/hooks/src/useEventBus/index.zh-CN.md b/packages/hooks/src/useEventBus/index.zh-CN.md deleted file mode 100644 index 7f85ef0697..0000000000 --- a/packages/hooks/src/useEventBus/index.zh-CN.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -nav: - path: /hooks ---- - -# useEventBus - -`useEventBus` 用法等同于 `Bus.$on`,但是可以依赖状态的值,依赖更新时重新挂载函数。订阅后任何地方都可以调用方法 - -## 代码演示 - -### 基础用法 - - - -## API - -```typescript -// hooks 订阅 -useEventBus('订阅方法名称',订阅回调方法,[依赖项]); -``` 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` |