mcpField
U
use-static-effect-event

use-static-effect-event

github.com/supabase开发114.3k89 次安装今天更新
代码仓库

关于 use-static-effect-event

useStaticEffectEvent 是 React 的 useEffectEvent 用户态 polyfill,为 React 开发者提供稳定回调,在不触发 Effect 重跑的情况下读取最新状态和属性。它用于 Effect 中的非响应式逻辑,并避免过时闭包。

主要功能

  1. 01稳定回调读取最新状态和属性
  2. 02避免过时闭包
  3. 03避免无关的 Effect 重跑
  4. 04支持异步操作和分页触发

适用场景

  1. 01在 Effect 中读取最新状态而不重新运行
  2. 02为异步操作创建稳定回调
  3. 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 中你会面临两个糟糕的选择:

  1. 将值添加到依赖项中 → 导致不必要的 Effect 重新运行(清理/重新连接)
  2. 从依赖项中省略值 → 导致闭包过时问题(使用过期的值)
// 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])

何时使用

  1. 在 Effect 中读取最新的 state/props,而不重新触发 Effect
  2. 创建始终使用当前值的稳定回调
  3. 避免在 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)

规则

  1. 只能在 Effect 内(useEffect、useLayoutEffect)调用返回的函数
  2. 不要将它作为回调 prop 传递给其他组件或 hook
  3. 仅用于非响应式逻辑——读取值,但不应触发重新运行
  4. 在 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