关于 use-static-effect-event
useStaticEffectEvent 是 React 的 useEffectEvent 用户态 polyfill,为 React 开发者提供稳定回调,在不触发 Effect 重跑的情况下读取最新状态和属性。它用于 Effect 中的非响应式逻辑,并避免过时闭包。
主要功能
- 01稳定回调读取最新状态和属性
- 02避免过时闭包
- 03避免无关的 Effect 重跑
- 04支持异步操作和分页触发
适用场景
- 01在 Effect 中读取最新状态而不重新运行
- 02为异步操作创建稳定回调
- 03在无限滚动中触发下一页加载
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ supabase/ supabase --skill use-static-effect-event安装后在 Claude Code 中输入/use-static-effect-event确认已加载。
详细介绍
useStaticEffectEvent
位于 apps/studio/hooks/useStaticEffectEvent.ts。
React 的 useEffectEvent 的用户态 polyfill(在 React 19.2 中稳定)。它解决了闭包过时问题:提供一个稳定的回调,始终读取最新的 props/state,同时不会因这些值而触发 Effect 重新运行。
它解决的问题
如果没有它,在 useEffect 中你会面临两个糟糕的选择:
- 将值添加到依赖项中 → 导致不必要的 Effect 重新运行(清理/重新连接)
- 从依赖项中省略值 → 导致闭包过时问题(使用过期的值)
// Problem: re-runs every time `theme` changes, even though we only
// want to reconnect when `roomId` changes
useEffect(() => {
const connection = createConnection(roomId)
connection.on('connected', () => {
showNotification('Connected!', theme) // theme causes unwanted reconnects
})
return () => connection.disconnect()
}, [roomId, theme])
何时使用
- 在 Effect 中读取最新的 state/props,而不重新触发 Effect
- 创建始终使用当前值的稳定回调
- 避免在 Effect 中使用的事件处理函数出现闭包过时问题
模式 1:同步数据而不在每次变化时重新运行
const syncApiPrivileges = useStaticEffectEvent(() => {
if (hasLoadedInitialData.current) return
if (!apiAccessStatus.isSuccess) return
if (!privilegesForTable) return
hasLoadedInitialData.current = true
setPrivileges(privilegesForTable.privileges)
})
useEffect(() => {
syncApiPrivileges()
}, [apiAccessStatus.status, syncApiPrivileges])
模式 2:为异步操作创建稳定回调
const exportInternal = useStaticEffectEvent(
async ({ bypassConfirmation }: { bypassConfirmation: boolean }) => {
if (!params.enabled) return
const { projectRef, connectionString, entity, totalRows } = params
// complex async logic using latest params
}
)
// Stable reference — safe to use in useCallback
const exportInDesiredFormat = useCallback(
() => exportInternal({ bypassConfirmation: false }),
[exportInternal]
)
模式 3:无限滚动/分页触发器
const fetchNext = useStaticEffectEvent(() => {
if (lastItem && lastItem.index >= items.length - 1 && hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
})
useEffect(fetchNext, [lastItem, fetchNext])
何时不要使用
不要用它来隐藏合理的依赖项:
// ❌ Bad — roomId IS a legitimate dependency; this hides a bug
const connect = useStaticEffectEvent(() => {
const connection = createConnection(roomId)
connection.connect()
})
useEffect(() => {
connect()
}, [connect]) // Won't reconnect when roomId changes!
// ✅ roomId belongs in deps
useEffect(() => {
const connection = createConnection(roomId)
connection.connect()
return () => connection.disconnect()
}, [roomId])
不要将它用于 Effect 之外的简单事件处理函数:
// ❌ Unnecessary — not used inside an Effect
const handleClick = useStaticEffectEvent(() => console.log(count))
// ✅ Regular function is fine
const handleClick = () => console.log(count)
规则
- 只能在 Effect 内(
useEffect、useLayoutEffect)调用返回的函数 - 不要将它作为回调 prop 传递给其他组件或 hook
- 仅用于非响应式逻辑——读取值,但不应触发重新运行
- 在
useEffect中使用时,将它加入依赖项数组(它是稳定的,不会导致重新运行)
工作原理
export const useStaticEffectEvent = <Callback extends Function>(callback: Callback) => {
const callbackRef = useRef(callback)
useLayoutEffect(() => {
callbackRef.current = callback // always latest
})
const eventFn = useCallback((...args: any) => {
return callbackRef.current(...args)
}, []) // stable reference
return eventFn as unknown as Callback
}
评论
支持 Markdown