节流函数(受 lodash 启发)仅使用 React 挂钩(使用 Sandbox)来处理调整大小事件

Throttle function (lodash inspired) to handle resize event using React hooks only (with Sandbox)

抱歉问了这么长的问题,但我需要做一个介绍才能更清楚。

我需要一些代码来在我的 Headers 组件 <HeaderDesktop><MobileHeader> 之间切换。

起初我使用 CSS 媒体查询,使用 display: block | none; 在它们之间切换。这不太理想,因为两个组件将同时呈现,这是低效的,并且可能会给隐藏元素上的广告显示带来问题。

有人在 SO 上建议我可以使用 window.innerWidth 并使用 React 来确定要渲染的组件。那确实好多了。现在一次只渲染 1 个组件。这就是我所做的:

// 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 throttledebounce 方法。两者都可以减少和控制处理的事件数量,即使随后触发了数百个事件。

https://css-tricks.com/debouncing-throttling-explained-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

问题 1

关于如何改进调整大小事件处理程序的节流版本的代码,您能给我一些建议吗?

问题 2

我猜我会在其他组件中需要该功能。我怎样才能使它易于重用?我可以将其设为自定义 Hook 吗?我从来没有创建过一个,所以我仍然在如何推理它们以及创建它们的正确方法方面遇到一些麻烦。你能帮我把它放到自定义挂钩中吗?

或者为此创建一个高阶组件会更好吗?

这不是我用钩子做的事。您可以让它作为一个挂钩工作,但您将自己限制在仅在组件内部进行节流,而节流是一个比这更有用的实用功能,而挂钩不会让它变得更容易或让您做任何额外的事情。

如果您不想导入所有 lodash,这是可以理解的,但您可以自己实现类似的东西。 Lodash 的 throttle 是一个高阶函数:你将它传递给一个函数,它 returns 你一个新函数,只有在自上次执行以来经过了适当的时间后才会执行。执行此操作的代码(没有像 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);
}, []);