-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuse-stable-handler.ts
More file actions
67 lines (63 loc) · 2.48 KB
/
use-stable-handler.ts
File metadata and controls
67 lines (63 loc) · 2.48 KB
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
import { useCallback, useEffect, useRef } from 'react';
/**
* useStableHandler
*
* 解决事件订阅场景中,handler 频繁变化导致反复 subscribe/unsubscribe 的问题。
*
* 核心思路:
* - 用 ref 持有最新的 handler,在独立 effect 中同步(不在渲染阶段直接赋值)
* - stableHandler 是稳定引用,始终代理到最新的 handlerRef.current
* - 订阅/取消订阅只在 subscribe 函数变化时重新执行
*
* 适用场景:WebSocket 事件、EventEmitter、原生 addEventListener 等
*
* ⚠️ subscribe 必须是稳定引用:在调用处用 useCallback 包裹,否则每次渲染都会重新订阅
*
* @param subscribe 订阅函数,接收 stableHandler,返回取消订阅函数(必须稳定,用 useCallback)
* @param handler 事件处理函数(可以是每次渲染新建的函数,不影响订阅稳定性)
*
* @example
* // Socket.IO — subscribe 用 useCallback 稳定
* const subscribe = useCallback((handler) => {
* socket.on('message:new', handler);
* return () => socket.off('message:new', handler);
* }, [socket]);
*
* useStableHandler(subscribe, (msg) => setMessages((prev) => [...prev, msg]));
*
* @example
* // 原生 DOM 事件
* const subscribe = useCallback((handler) => {
* window.addEventListener('resize', handler);
* return () => window.removeEventListener('resize', handler);
* }, []);
*
* useStableHandler(subscribe, () => setWidth(window.innerWidth));
*/
export function useStableHandler<T>(
subscribe: (handler: (payload: T) => void) => () => void,
handler: (payload: T) => void,
): void {
const handlerRef = useRef(handler);
// 在 effect 中更新 ref,避免渲染阶段的副作用(Strict Mode 安全)
useEffect(() => {
handlerRef.current = handler;
}, [handler]);
useEffect(() => {
const stableHandler = (payload: T) => handlerRef.current(payload);
const unsubscribe = subscribe(stableHandler);
return unsubscribe;
}, [subscribe]);
}
// ------------------------------------------------------------
// React 19+ 替代方案:useEffectEvent(更简洁,无需手动管理 ref)
// ------------------------------------------------------------
// import { useEffectEvent } from 'react';
//
// export function useStableHandler<T>(
// subscribe: (handler: (payload: T) => void) => () => void,
// handler: (payload: T) => void,
// ): void {
// const stableHandler = useEffectEvent(handler);
// useEffect(() => subscribe(stableHandler), [subscribe]);
// }