封装一个在浏览器窗口变化时更新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;
相关推荐
风无雨2 分钟前
GO启动一个视频下载接口 前端可以边下边放
前端·golang·音视频
Rainbow_Pearl7 分钟前
Vue2_element 表头查询功能
javascript·vue.js·elementui
aha-凯心43 分钟前
前端学习 vben 之 axios interceptors
前端·学习
熊出没1 小时前
Vue前端导出页面为PDF文件
前端·vue.js·pdf
VOLUN1 小时前
Vue3项目中优雅封装API基础接口:getBaseApi设计解析
前端·vue.js·api
此乃大忽悠1 小时前
XSS(ctfshow)
javascript·web安全·xss·ctfshow
用户99045017780091 小时前
告别广告干扰,体验极简 JSON 格式化——这款工具让你专注代码本身
前端
前端极客探险家1 小时前
告别卡顿与慢响应!现代 Web 应用性能优化:从前端渲染到后端算法的全面提速指南
前端·算法·性能优化
袁煦丞2 小时前
【局域网秒传神器】LocalSend:cpolar内网穿透实验室第418个成功挑战
前端·程序员·远程工作
江城开朗的豌豆2 小时前
Vuex数据突然消失?六招教你轻松找回来!
前端·javascript·vue.js