简体   繁体   English

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

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

Sorry for the long question, but I needed to make an introduction in order to make it clearer.抱歉问了这么长的问题,但我需要做一个介绍以使其更清楚。

I was in need of some code to toggle between my Headers components <HeaderDesktop> and <MobileHeader> .我需要一些代码来在我的 Headers 组件<HeaderDesktop><MobileHeader>之间切换。

At first I was using CSS media queries to toggle between them using display: block | none;起初我使用 CSS 媒体查询在它们之间切换使用display: block | none; display: block | none; . . This is less then ideal, since both components will be rendered at the same time, which is inneficient and could bring problems in advertisement displays on hidden elements.这不太理想,因为两个组件将同时呈现,这是低效的并且可能会在隐藏元素上的广告显示中带来问题。

Someone here on SO suggested that I could use window.innerWidth and use React to determine which component to render based on that. SO 上的某个人建议我可以使用window.innerWidth并使用 React 来确定要基于它呈现的组件。 That is much better indeed.那确实好多了。 Now only 1 component gets rendered at a time.现在一次只渲染 1 个组件。 This is what I did:这就是我所做的:

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

But I needed a way to handle resize events.但我需要一种处理调整大小事件的方法。 So I did:所以我做了:

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

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

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

Nice!好的! That works, but now my component renders 1 trillion times a second every time a resize is happening.这有效,但现在我的组件在每次调整大小时每秒渲染 1 万亿次。 That's no good for performance.这对性能没有好处。

So I've done my research and found out about lodash throttle and debounce methods.所以我完成了我的研究并发现了lodash throttledebounce方法。 Both can reduce and control the number of events handled, even when hundreds are fired subsequentely.两者都可以减少和控制处理的事件数量,即使随后触发了数百个事件也是如此。

https://css-tricks.com/debouncing-throttling-explained-examples/ https://css-tricks.com/debouncing-throttling-explained-examples/

But I'm not a fan of bringing entire libraries to my dependency list just to use a simple functionality like that, so I've ended up creating the following effect hook to mimic the throttle functionality on my resize event handler.但是我不喜欢将整个库加入我的依赖项列表只是为了使用这样的简单功能,所以我最终创建了以下效果钩子来模拟我的resize事件处理程序上的throttle功能。

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

You can see this in action in the following Sandbox:您可以在以下沙箱中看到这一点:

https://codesandbox.io/s/v3o0nmvvl0 https://codesandbox.io/s/v3o0nmvvl0

在此处输入图片说明

QUESTION 1问题 1

Can you give me any suggestions on how to improve my code for the throttled version of the resize event handler?您能否就如何改进调整大小事件处理程序的节流版本的代码给我任何建议?

QUESTION 2问题2

I'm guessing I'll be needing that functionality in other components.我猜我会在其他组件中需要该功能。 How can I make this easily reusable?我怎样才能使它易于重复使用? Can I make this a custom Hook?我可以把它做成一个定制的 Hook 吗? I have never created one, so I'm still having some trouble on how to reason about them and what is the proper way to create them.我从未创建过一个,所以我仍然在如何推理它们以及创建它们的正确方法方面遇到一些麻烦。 Can you help me to put that into a Custom Hook?你能帮我把它放到自定义钩子里吗?

Or would it better to create a Higher Order Component for that?还是为此创建一个高阶组件会更好?

This isn't something i'd do with a hook.这不是我会用钩子做的事情。 You can get it to work as a hook, but you're limiting yourself to only doing throttling inside components, when throttling is a more useful utility function than that, and hooks don't make it any easier or let you do anything extra.你可以让它作为一个钩子工作,但你限制自己只在组件内部进行节流,当节流是一个比这更有用的实用函数时,钩子不会让它变得更容易,也不会让你做任何额外的事情。

If you don't want to import all of lodash, that's understandable, but you could implement something similar yourself.如果您不想导入所有 lodash,这是可以理解的,但您可以自己实现类似的东西。 Lodash's throttle is a higher order function: you pass it in a function, and it returns you a new function that will only execute if the appropriate amount of time has passed since the last execution. Lodash 的油门是一个高阶函数:你将它传递给一个函数,它会返回一个新函数,该函数只会在自上次执行后经过适当的时间后才会执行。 The code to do that (without quite as many options and safety checks as lodash does) can be replicated like this:执行此操作的代码(没有像 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);
  }
}

To be used like this:像这样使用:

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

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

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

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