封装一个在浏览器窗口变化时更新React组件的hook

有些时候,我们需要实现在视窗变化时,随着窗口一同变化的弹性UI。这次的需求中,也是需要随着视窗的变化,实时改变子元素 iframe 的高度,使得父页面上不会出现滚动条。

监听 resize 事件并重新渲染组件

React 中没有内置的 resize 事件,但我们可以从组件中去监听浏览器的原生窗口调整大小 resize 事件:

js 复制代码
import { useState, useEffect } from 'react';

function useResize() {
  const [windowHeight, setWindowHeight] = useState(window.innerHeight);

  useEffect(() => {

    const handleResize = () => {
      setWindowHeight(window.innerHeight);
    }

    window.addEventListener('resize', handleResize);
    }, [windowHeight]);

  return windowHeight;
}

export default useResize;

当 window 检测到 resize 事件时,react 会调用 setWindowHeight,使得 state 刷新,重新渲染组件。

清除事件监听

当我们添加事件监听器时,比如 resize 事件,我们应该确保在组件卸载时清理干净。在以上示例中,我们没有清除掉监听器,这可能会给我们的应用带来问题。

React会在每次监听到事件时刷新组件执行。在每次 resize 执行,windowHeight 改变状态,而组件重新渲染时,useEffect都会被调用一次。这将为 resize 事件创建 n 个新的 handleResize 事件绑定。如果此组件经常被重新渲染,这可能会在我们的程序中造成严重的内存泄漏。我们只需要一个事件监听器,即我们需要在创建新监听器之前清理已建立的事件监听器。

在 React 中,当向 useEffect 传递一个函数时,如果该函数也返回一个函数,那么返回的函数将被用来执行任何必要的清理操作。我们可以将 removeEventListener 代码放在 useEffect 的 return 中:

js 复制代码
import { useState, useEffect } from 'react';

function useResize() {
  const [windowHeight, setWindowHeight] = useState(window.innerHeight);

  useEffect(() => {

    const handleResize = () => {
      setWindowHeight(window.innerHeight);
    }

    window.addEventListener('resize', handleResize);
    }, [windowHeight]);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
    
  return windowHeight;
}

export default useResize;

为 resize 事件加上防抖

目前,我们的示例代码设置为每当视窗大小变化时即调用 handleResize。浏览器也是正以尽可能快的频率为每个像素变化设置状态和重新渲染,不得不考虑性能问题。如果有充分的理由不需要那么频繁地处理 resize,就会希望出于性能原因(例如渲染速度较慢或渲染成本较高的组件)而减少重新渲染的频率。

在这种情况下,我们可以对 resize 事件的监听处理进行防抖,从而减少重新渲染的频率。有很多可靠的防抖实现。我们在代码中添加一个简短而简单的实现:

js 复制代码
import { useState, useEffect } from 'react';

function useDebouncedResize() {
  const [windowHeight, setWindowHeight] = useState(window.innerHeight);

  useEffect(() => {
    function debounce(fn: () => void, interval: number) {
      let timer: any;
      return () => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          timer = null;
          fn.apply(this);
        }, interval);
      };
    }

    const debouncedHandleResize = debounce(() => {
      setWindowHeight(window.innerHeight);
    }, 1000);

    window.addEventListener('resize', debouncedHandleResize);

    return () => {
      window.removeEventListener('resize', debouncedHandleResize);
    };
  }, [windowHeight]);

  return windowHeight;
}

export default useDebouncedResize;

我们将 handleResize 包裹在 debounce() 调用中,并将返回的新函数绑定到 debouncedHandleResize 变量。

debounce()的第二个参数是1000ms,则意味着我们将确保 handleResize 代码最多每秒调用一次。

使用

接下来,就可以在我们的应用中调用这个 hook,来获取视窗的实时高度咯

js 复制代码
import React from 'react';
import useDebouncedResize from './useDebouncedResize';

function MyComponent() {
  const windowHeight = useDebouncedResize();

  // 在这里使用windowHeight进行其他操作

  return (
    // JSX代码
  );
}

export default MyComponent;
相关推荐
伍哥的传说9 分钟前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi98783811 分钟前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
浪裡遊22 分钟前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听11 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿1 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉1 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
归于尽2 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课2 小时前
React useEffect 详解与运用
前端·react.js
我想说一句2 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript
funnycoffee1232 小时前
Huawei 6730 Switch software upgrade example版本升级
java·前端·华为