You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Hook中ESLint提示赋值丢失,是否应忽略该警告?

自定义React Hook的ESLint警告分析与处理

问题背景

你编写了如下自定义React Hook:

type Callback = () => void

function useElementEffect<T extends Element>(callback: (el: T) => (Callback | void)) {
    let cb: Callback | void
    return useCallback((el: T) => {
        if(el) {
            cb = callback(el)  // <-- eslint error here
        } else if(cb) {
            cb()
        }
    }, [])
}

使用示例:

function CenterCanvas() {
    const [[width, height], setSize] = useState([300, 150])
    const ref = useElementEffect((div: HTMLDivElement) => {
        const obs = new ResizeObserver(entries => {
            for(const entry of entries) {
                const contentBoxSize = entry.contentBoxSize[0]
                setSize([
                    Math.floor(contentBoxSize.inlineSize),
                    Math.floor(contentBoxSize.blockSize)
                ])
            }
        })
        obs.observe(div)
        return () => obs.disconnect()
    })

    return (
        <$Center ref={ref}>
            <TheCanvas width={width} height={height}/>
        </$Center>
    )
}

该Hook运行正常,但cb = callback(el)行触发ESLint警告:

ESLint: Assignments to the 'cb' variable from inside React Hook useCallback will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useCallback.(react-hooks/exhaustive-deps)

你发现组件卸载时cb()能正常执行,想知道是否该忽略警告,或存在潜在失效场景。

为什么当前能正常运行?

当前代码能正常工作的核心原因是:useCallback依赖空数组,返回的ref回调函数是稳定的闭包——组件每次渲染时,这个函数的引用都不会变,它捕获的是第一次调用useElementEffect时声明的cb变量。只要这个闭包存在(组件未卸载),cb的值就会被保留,所以组件卸载时React调用ref回调传入null,能正常触发之前保存的清理函数。

潜在失效场景

虽然当前场景没问题,但存在以下隐患:

  1. callback更新失效:如果useElementEffect的callback参数在组件后续渲染中发生变化(比如示例中CenterCanvas的内联回调会在每次渲染时重新创建),但useCallback的依赖数组为空,不会更新ref回调函数,导致后续元素挂载时仍然调用旧的callback,新的逻辑无法生效,旧的清理函数也可能无法被正确替换。
  2. 闭包变量混淆:每次组件渲染时,useElementEffect都会重新声明一个新的cb变量,但ref回调函数始终引用第一次渲染的cb。如果后续渲染中存在其他操作意外修改这个变量,可能导致难以排查的逻辑错误。
  3. 不符合React Hooks规范:直接在Hook作用域中声明可变变量并在闭包中修改,不属于React推荐的状态管理方式,容易引发闭包陷阱,降低代码可维护性。

正确的处理方式

按照ESLint提示,用useRef保存清理函数,这是React Hooks中保存跨渲染可变值的标准做法:

type Callback = () => void

function useElementEffect<T extends Element>(callback: (el: T) => (Callback | void)) {
    const cbRef = useRef<Callback | void>()
    return useCallback((el: T) => {
        if(el) {
            cbRef.current = callback(el)
        } else if(cbRef.current) {
            cbRef.current()
            cbRef.current = undefined // 清理引用,避免内存泄漏
        }
    }, [callback]) // 加入callback依赖,确保回调更新时能获取最新逻辑
}

改进点说明:

  • 用useRef的.current属性存储清理函数,它在组件整个生命周期中是同一个引用,不受渲染影响。
  • 将callback加入useCallback的依赖数组,确保callback更新时,ref回调函数能同步更新,调用最新的逻辑。
  • 执行清理后重置cbRef.current,避免不必要的内存占用。

结论

不要忽略ESLint警告,用useRef重构代码能消除潜在的失效场景,同时符合React Hooks的最佳实践,提升代码的稳定性和可维护性。

内容的提问来源于stack exchange,提问作者mpen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 13:35:19