在我的React自定义钩子的每次调用中添加和删除事件侦听器是否太昂贵?如何避免呢?

cbd*_*per 5 javascript reactjs react-hooks

这是我的情况:

我有一个名为的自定义钩子,useClick它获取HTML element和a callback作为输入,将click 事件监听器附加到该钩子,element并将the设置callback事件处理程序

App.js

function App() {
  const buttonRef = useRef(null);
  const [myState, setMyState] = useState(0);

  function handleClick() {
    if (myState === 3) {
      console.log("I will only count until 3...");
      return;
    }
    setMyState(prevState => prevState + 1);
  }

  useClick(buttonRef, handleClick);

  return (
    <div>
      <button ref={buttonRef}>Update counter</button>
      {"Counter value is: " + myState}
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

useClick.js

import { useEffect } from "react";

function useClick(element, callback) {
  console.log("Inside useClick...");

  useEffect(() => {
    console.log("Inside useClick useEffect...");
    const button = element.current;

    if (button !== null) {
      console.log("Attaching event handler...");
      button.addEventListener("click", callback);
    }
    return () => {
      if (button !== null) {
        console.log("Removing event handler...");
        button.removeEventListener("click", callback);
      }
    };
  }, [element, callback]);
}

export default useClick;
Run Code Online (Sandbox Code Playgroud)

注意,使用上面的代码,我将在每次调用此钩子时添加和删除事件侦听器。

我非常想仅在安装时添加,在卸除时删除。而不是添加和删除每个呼叫。

即使我正在使用此:

useEffect(()=>{
  // Same code as above
},[element,callback]);  // ONLY RUN THIS WHEN 'element' OR 'callback' CHANGES
Run Code Online (Sandbox Code Playgroud)

这样做的问题是,即使elementref)在所有渲染中都保持相同,callback这是App的handleClick函数)也会在每个渲染中发生变化。所以我最终还是在每个渲染上添加和删除了处理程序。

我也不能转换handleCLickuseCallback,因为它取决于状态myState变量,该状态变量在每个渲染上都会更改,并且useCallback仍然会在每个渲染上(当myState更改时)重新创建我,因此问题仍然存在。

const handleClick = useCallback(()=> {
  if (myState === 3) {
    console.log("I will only count until 3...");
    return;
  }
  setMyState(prevState => prevState + 1);
},[myState]); // THIS WILL CHANGE ON EVERY RENDER!
Run Code Online (Sandbox Code Playgroud)

题:

CodeSandbox上的示例

我是否应该担心在每个渲染器上都删除并附加监听器?还是这真的完全不贵,有没有伤害我的表现的机会?有没有办法避免呢?

tri*_*ixn 4

您可以将当前回调存储在 ref 中,并为仅调用当前回调的事件侦听器提供静态回调:

function useClick(element, callback) {
    console.log('Inside useClick...');

    const callbackRef = useRef(callback);

    useEffect(() => {
        callbackRef.current = callback;
    }, [callback]);

    const callbackWrapper = useCallback(props => callbackRef.current(props), []);

    useEffect(() => {
        console.log('Inside useClick useEffect...');
        const button = element.current;

        if (button !== null) {
            console.log('Attaching event handler...');
            button.addEventListener('click', callbackWrapper);
        }
        return () => {
            if (button !== null) {
                console.log('Removing event handler...');
                button.removeEventListener('click', callbackWrapper);
            }
        };
    }, [element, callbackWrapper]);
}
Run Code Online (Sandbox Code Playgroud)

工作示例:

编辑7wxqxwx1rj