Не слишком ли дорого добавлять и удалять прослушиватели событий при каждом вызове моего пользовательского обработчика React? Как этого избежать? - PullRequest
5 голосов
/ 15 мая 2019

Вот моя ситуация:

У меня есть пользовательский хук, называемый useClick, который получает HTML element и callback в качестве входных данных, присоединяет click прослушиватель событий к этому element, и устанавливает 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>
  );
}

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;

Обратите внимание, что с кодом выше, Я буду добавлять и удалять прослушиватель событий при каждом вызове этой ловушки.

И я бы очень хотел, чтобы добавлял только при монтировании и удалял при размонтировании. Вместо добавления и удаления при каждом вызове.

Даже если я использую это:

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

Проблема в том, что даже если element ( ref ) останется неизменным во всех рендерах, callback (, которая является функцией handleClick из приложения ), изменить на каждом рендере. Поэтому я все равно добавляю и удаляю обработчики на каждом рендере.

Я также не могу преобразовать handleCLick в useCallback, потому что это зависит от переменной состояния 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!

ВОПРОС:

Пример для CodeSandbox

Должен ли я беспокоиться об удалении и подключении даже слушателей на каждом рендере? Или это действительно совсем не дорого и не имеет шансов навредить моей производительности ? Есть ли способ избежать этого?

1 Ответ

3 голосов
/ 15 мая 2019

Вы можете сохранить текущий обратный вызов в ссылке и иметь статический обратный вызов для прослушивателя событий, который вызывает только текущий обратный вызов:

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

Рабочий пример:

Edit 7wxqxwx1rj

...