封装一个在浏览器窗口变化时更新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;
相关推荐
胡萝卜术5 小时前
从API调用到手写LRU:力扣146「LRU缓存」的哈希表+双向链表终极进化之路
前端·javascript·面试
科技道人5 小时前
记录 默认置灰/禁用 app ‘Search Engine Selector‘ 的disable按钮
开发语言·前端·javascript
天疆说5 小时前
Zotero Connector 在 Edge 里找不到桌面 Zotero(Linux / Zotero 9.0.6 / Edge 150)
linux·前端·edge
李伟_Li慢慢7 小时前
02-从硬件说起WebGL
前端·three.js
kyriewen8 小时前
看了微软几万人用AI编程的数据——效率涨24%的代价没人提
前端·ai编程·claude
2601_963771378 小时前
How to Scale Your WordPress Store Traffic in 2026
前端·php·plugin
亿元程序员8 小时前
老板说我的3D箭头游戏用来做试玩太普通了,没人想玩,让我变点花样...
前端
李伟_Li慢慢8 小时前
01-threejs架构原理-课程简介
前端·three.js
_瑞10 小时前
深入理解 iOS 渲染原理
前端·ios
IT_陈寒10 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端