封装一个在浏览器窗口变化时更新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;
相关推荐
小码哥_常1 分钟前
Kotlin类型魔法:Any、Unit、Nothing 深度探秘
前端
Web极客码1 小时前
深入了解WordPress网站访客意图
服务器·前端·wordpress
幺风2 小时前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
vjmap2 小时前
唯杰地图CAD图层加高性能特效扩展包发布
前端·gis
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day7(AI异步加速+卫星系列精简+AI Agent自动评论)
前端·人工智能·3d·html·json
ID_180079054732 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json
M ? A2 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
vipbic2 小时前
独立开发复盘:我用 Uni-app + Strapi v5 肝了一个“会上瘾”的打卡小程序
前端·微信小程序
IT_陈寒3 小时前
Vite的热更新突然失效,原来是因为这个配置
前端·人工智能·后端
ZC跨境爬虫4 小时前
3D 地球卫星轨道可视化平台开发 Day8(分步渲染200颗卫星+ 前端分页控制)
前端·python·3d·重构·html