首页 > 解决方案 > 仅使用 React 钩子(使用 Sandbox)来处理调整大小事件的节流函数(lodash 启发)

问题描述

很抱歉这个问题很长,但我需要做一个介绍以使其更清楚。

我需要一些代码来在我的 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

你能给我一些关于如何改进我的 resize 事件处理程序的限制版本的代码的建议吗?

问题2

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

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

标签: javascriptreactjsreact-hooks

解决方案


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

如果您不想导入所有 lodash,这是可以理解的,但是您可以自己实现类似的东西。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);
}, []);

推荐阅读