有些时候,我们需要实现在视窗变化时,随着窗口一同变化的弹性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;