简体   繁体   English

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

[英]Which one has better performance: add and remove event listener on every render VS running an useEffect to update a ref

Here's my situation: 这是我的情况:

I've got a custom hook, called useClick , which gets an HTML element and a callback as input, attaches a click event listener to that element , and sets the callback as the event handler . 我有一个名为useClick的自定义钩子,它获取一个HTML element和一个callback作为输入,将一个click 事件监听器附加到该element ,并将callback设置为事件处理程序

App.js 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>
  );
}

useClick.js 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;

Note that with the code above, I'll be adding and removing the event listener on every call of this hook (because the callback, which is handleClick changes on every render). 请注意,使用上面的代码, 我将在每次调用此钩子时添加和删除事件侦听器 (因为回调, which is handleClick每次渲染时which is handleClick更改)。 And it must change, because it depends on the myState variable, that changes on every render. 它必须改变,因为它取决于myState变量,它在每个渲染上都会发生变化。

And I would very much like to only add the event listener on mount and remove on dismount. 而且我非常希望只在mount上添加事件监听器并在卸载时删除。 Instead of adding and removing on every call. 而不是在每次通话时添加和删除。


Here on SO, someone have suggested that I coulde use the following: 在SO上,有人建议我使用以下内容:

useClick.js 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]);
}

QUESTION

It works as intended. 它按预期工作。 It only adds the event listener on mount, and removes it on dismount. 它只在mount上添加事件监听器,并在卸载时将其删除。

The code above uses a callback wrapper that uses a ref that will remain the same across renders (so I can use it as the event handler and mount it only once), and its .current property it's updated with the new callback on every render by a useEffect hook. 上面的代码使用了一个回调包装器,它使用一个ref,它将在渲染器中保持相同 (所以我可以将它用作事件处理程序并只挂载一次),并且它的.current属性在每次渲染时都使用新的回调进行更新。一个useEffect钩子。

The question is: performance-wise, which approach is the best? 问题是:性能方面,哪种方法最好? Is running a useEffect() hook less expensive than adding and removing event listeners on every render? 运行useEffect()挂钩比在每个渲染上添加和删除事件侦听器要便宜吗?

Is there anyway I could test this? 无论如何我可以测试一下吗?

App.js App.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>
  );
}

A more professional answer: 一个更专业的答案:

App.js App.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>
  );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM