Функция Throttle (вдохновленная lodash) для обработки события изменения размера только с помощью React-хуков (с песочницей) - PullRequest
1 голос
/ 03 мая 2019

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

Мне нужен был какой-то код для переключения между моими компонентами заголовков <HeaderDesktop> и <MobileHeader>.

Сначала я использовал медиазапросы CSS для переключения между ними с помощью display: block | none;.Это менее чем идеально, так как оба компонента будут отображаться одновременно, что неэффективно и может привести к проблемам при отображении рекламы на скрытых элементах.

Кто-то здесь, на SO, предложил мне использовать window.innerWidth ииспользуйте React, чтобы определить, какой компонент визуализировать на основе этого.Это действительно намного лучше.Теперь только один компонент отображается одновременно.Вот что я сделал:

// INSIDE HEADER COMPONENT
return(
  <HeaderWrapper>
   {window.innerWidth < 1200 ?
      <HeaderMobile/>
    : <HeaderDesktop/>
   }
  </HeaderWrapper>
);

Но мне нужен был способ обработки событий изменения размера.Так я и сделал:

// INSIDE HEADER COMPONENT
const [windowSize, setWindowSize] = useState(window.innerWidth);

function handleResize() {
  setWindowSize(window.innerWidth);
}

return(
  <HeaderWrapper>
   {windowSize < 1200 ?
      <HeaderMobile/>
    : <HeaderDesktop/>
   }
  </HeaderWrapper>
);

Отлично!Это работает, но теперь мой компонент рендерит 1 триллион раз в секунду каждый раз, когда происходит изменение размера.Это плохо для производительности.

Итак, я провел исследование и выяснил методы lodash throttle и debounce.Оба могут уменьшить и контролировать количество обработанных событий, даже если сотни запускаются последовательно.

https://css -tricks.com / debouncing-throttling-объясн-examples /

Но я не фанат включения целых библиотек в свой список зависимостей только для того, чтобы использовать простую функциональность, подобную этой, поэтому я закончил тем, что создал следующий хук для эффекта, имитирующий функциональность throttle для моего события resizeобработчик.

// INSIDE HEADER COMPONENT

// Ref to store if there's a resize in progress
const resizeInProgress = useRef(false);

// State to store window size
const [windowSize, setWindowSize] = useState(window.innerWidth);

useEffect(() => {

  // This function trigger the resize event handler
  // And updates the ref saying that there's a resize in progress
  // If theres a resize in progress, it doesn't do anything

  function handleResize() {
    if (resizeInProgress.current === true) {
      return;
    }
    resizeInProgress.current = true;
    throttled_updateWindowSize();
  }

  // This function sets a timeout to update the state with the
  // new window size and when it executes, it resets the
  // resizeInProgress ref to false. You can execute what's the interval
  // You want to handle your resize events, in this case is 1000ms

  function throttled_updateWindowSize() {
    setTimeout(() => {
      console.log("I will be updated!");
      console.log(window.innerWidth);
      setWindowSize(window.innerWidth);
      resizeInProgress.current = false;
    }, 1000);
  }


  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

Вы можете увидеть это в действии в следующей песочнице:

https://codesandbox.io/s/v3o0nmvvl0

enter image description here

ВОПРОС 1

Можете ли вы дать мне какие-либо предложения о том, как улучшить мой код для дросселированной версии обработчика событий изменения размера?

ВОПРОС 2

Я предполагаю, что мне понадобятся эти функции в других компонентах.Как я могу сделать это легко многоразовым?Могу ли я сделать этот кастомный крючок?Я никогда не создавал его, поэтому у меня все еще есть проблемы с тем, как рассуждать о них и как правильно их создавать.Можете ли вы помочь мне поместить это в Custom Hook?

Или лучше создать для этого компонент высшего порядка?

1 Ответ

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

Это не то, что я бы сделал с крючком. Вы можете заставить его работать как ловушка, но вы ограничиваетесь только тем, что делаете регулирование внутри компонентов, когда регулирование является более полезной утилитарной функцией, чем это, и ловушки не облегчают это или позволяют вам делать что-то дополнительное.

Если вы не хотите импортировать весь lodash, это понятно, но вы могли бы реализовать нечто подобное самостоятельно. Дроссель Лодаша - это функция более высокого порядка: вы передаете ее в функцию, и она возвращает вам новую функцию, которая будет выполняться только в том случае, если с момента последнего выполнения прошло соответствующее количество времени. Код для этого (без такого количества опций и проверок безопасности, как у lodash) может быть скопирован следующим образом:

const throttle = (func, delay) => {
  let inProgress = false;
  return (...args) => {
    if (inProgress) {
      return;
    }
    inProgress = true;
    setTimeout(() => {
      func(...args); // Consider moving this line before the set timeout if you want the very first one to be immediate
      inProgress = false;
    }, delay);
  }
}

Для использования следующим образом:

useEffect(() => {
  const handleResize = throttle(() => {
    setWindowSize(window.innerWidth);
  }, 1000);

  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []
...