哪一个性能更好:在运行useEffect的每个渲染VS上添加和删除事件侦听器以更新引用

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)

请注意,使用上面的代码,我将在此钩子的每次调用中添加和删除事件侦听器(因为回调函数which is handleClick会在每个渲染器上进行更改)。而且必须更改,因为它取决于myState变量,所以每个渲染都必须更改。

我非常想仅在装入时添加事件侦听器,在卸除时删除事件侦听器。而不是添加和删除每个呼叫。


在这里,有人建议我可以使用以下内容:

useClick.js

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)

它按预期工作。它仅在装入时添加事件监听器,而在卸除时将其删除。

上面的代码使用了一个回调包装,该包装使用的ref将在渲染器之间保持不变(因此我可以将其用作事件处理程序,并且只能将其安装一次),并且其.current属性将通过useEffect钩子在每个渲染器上使用新的回调进行更新。。

问题是:从性能角度考虑,哪种方法最好?运行useEffect()钩子是否比在每个渲染器上添加和删除事件侦听器便宜?

反正我可以测试一下吗?

小智 1

应用程序.js

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

  // handleClick remains unchanged
  const handleClick = useCallback(
    () => setMyState(prevState => prevState >= 3 ? 3 : prevState + 1),
    []
  );

  useClick(buttonRef, handleClick);

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

更专业的回答:

应用程序.js

function App() {
  const buttonRef = useRef(null);
  const [myState, handleClick] = useReducer(
    prevState => prevState >= 3 ? 3 : prevState + 1,
    0
  );

  useClick(buttonRef, handleClick);

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