且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

“useEffect 缺少依赖项"吗?警告有时出错?

更新时间:2022-06-12 22:19:58

我个人总是禁用 eslint 规则.由于 eslint 无法理解你的逻辑内涵,它只能彻底检查闭包中捕获的所有变量,并警告你从 dep-list 中丢失的变量.但是很多时候它是矫枉过正的,就像在您的用例中一样.这就是我选择的理由.

I personally always disable that eslint rule. Due to that eslint has no way to understand your logical intension, it can only exhaustively check all variables captured in the closure and warn you about missing ones from dep-list. But a lot of time it's overkilling, just like in your use case. That's the reasoning behind my choice.

如果您清楚地了解 useEffect 的工作原理,禁用此规则不会造成太大的痛苦.我个人不记得经历过.

If you have a clear understanding of how useEffect works, disabling this rule wouldn't cause much pain. I personally don't remember experiencing it.

第二种解决方案是保留规则,但要解决它.我为你准备了一个,useFn 自定义钩子:

A second solution is to leave the rule on, but work around it. I got one for you, the useFn custom hook:

function useFn(fn) {
  const ref = useRef(fn);
  ref.current = fn;

  function wrapper() {
    return ref.current.apply(this, arguments)
  }

  return useRef(wrapper).current
}

这个钩子返回一个wrapper函数的稳定引用,它只是一个调用实际fn的代理,但在重新渲染时不会改变.>

This hook returns a stable reference of wrapper function, which is just a proxy that call the actual fn, but doesn't change across re-rendering.

const SimpleComponent = (props: Props) => {
    const {id, callback: _callback} = props;

    const callback = useFn(_callback)
    
    useEffect(() => {
        callback(id)
    }, [id, callback]);

    return <div/>
};

现在你满足了 eslint 规则,同时你不会触发不需要的 useEffect 重新运行.

Now you satisfy that eslint rule meanwhile you don't trigger unwanted useEffect re-run.

作为题外话.我还使用 useFn 钩子来包装传递给子组件的 props 的函数.

As an off-topic side note. I also use useFn hook to wrap functions that got passed to child components' props.

传递箭头函数是 React 中大量使用的模式.有时你有一个重新渲染的组件很昂贵,你 React.memo(Component) 包装它,然后你传递一个 { ... }}/> 内联函数,有效地使 memoize 效果失效.useFn 来拯救:

Passing arrow function around is heavily used pattern in React. Sometimes you have a component that's expensive to re-render, you React.memo(Component) wrap it, yet then you pass a <Component onClick={e => { ... }} /> inline function, which effectively invalidate the memoize effect. useFn comes to rescue:

<Component onClick={useFn(e => { ... })} />