如何向 useElementSize 挂钩添加去抖动?
How to add debounce to useElementSize hook?
我正在使用以下钩子来获取元素的宽度和高度:
import { useCallback, useLayoutEffect, useState } from "react";
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size,
] {
function debounce(func: Function) {
let timer: any;
return function (event: any) {
if (timer) clearTimeout(timer);
timer = setTimeout(func, 100, event);
};
}
// Mutable values like 'ref.current' aren't valid dependencies
// because mutating them doesn't re-render the component.
// Instead, we use a state as a ref to be reactive.
const [ref, setRef] = useState<T | null>(null);
const [size, setSize] = useState<Size>({
width: 0,
height: 0,
});
// Prevent too many rendering using useCallback
const handleSize = useCallback(() => {
if (ref?.offsetWidth && ref?.offsetHeight) {
setSize({
width: ref?.offsetWidth || 0,
height: ref?.offsetHeight || 0,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useLayoutEffect(() => {
window.addEventListener(
"resize",
debounce(function () {
handleSize();
}),
);
handleSize();
return () => window.removeEventListener("resize", handleSize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref?.offsetHeight, ref?.offsetWidth]);
return [setRef, size];
}
export default useElementSize;
它完美地工作 - 当我调整 window 的大小时,我发现每次我调整大小时组件都会重新呈现,而且它发生得非常快。
问题是我有一个非常大的元素,我想为 useElementHook 添加去抖动。我尝试添加 2 秒的去抖动,但出现以下行为:
这是我试过的:
import { useCallback, useLayoutEffect, useState } from "react";
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size,
] {
function debounce(func: Function) {
let timer: any;
return function (event: any) {
if (timer) clearTimeout(timer);
timer = setTimeout(func, 2000, event);
};
}
// Mutable values like 'ref.current' aren't valid dependencies
// because mutating them doesn't re-render the component.
// Instead, we use a state as a ref to be reactive.
const [ref, setRef] = useState<T | null>(null);
const [size, setSize] = useState<Size>({
width: 0,
height: 0,
});
// Prevent too many rendering using useCallback
const handleSize = useCallback(() => {
if (ref?.offsetWidth && ref?.offsetHeight) {
setSize({
width: ref?.offsetWidth || 0,
height: ref?.offsetHeight || 0,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useLayoutEffect(() => {
window.addEventListener(
"resize",
debounce(function () {
handleSize();
}),
);
handleSize();
return () => window.removeEventListener("resize", handleSize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref?.offsetHeight, ref?.offsetWidth]);
return [setRef, size];
}
export default useElementSize;
当前行为:
调整大小事件 -> 什么都不做 -> 2 秒过去了 -> 我看到 40 console.logs(组件仍然重新渲染 40 次而不是一次)。
我想要的:
调整大小事件 -> 什么都不做 -> 2 秒过去了 -> 1 console.log 并重新渲染组件一次!
有解决办法吗?
我已经对您的实现进行了一些更改以使其正常工作:
显着变化:
- 将
handleSize
移到效果内(不需要 useCallback
- 使用对节点的引用以避免对效果的依赖
import { useLayoutEffect, useRef, useState } from "react";
interface Size {
width: number;
height: number;
}
function debounce(func: Function) {
let timer: any;
return function (event: any) {
if (timer) clearTimeout(timer);
timer = setTimeout(func, 2000, event);
};
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size
] {
const [node, setNode] = useState<T | null>(null);
const nodeRef = useRef(node);
const [size, setSize] = useState<Size>({
width: 0,
height: 0
});
useLayoutEffect(() => {
nodeRef.current = node;
}, [node]);
useLayoutEffect(() => {
const handleSize = () => {
if (nodeRef.current?.offsetWidth && nodeRef.current?.offsetHeight) {
setSize({
width: nodeRef.current?.offsetWidth || 0,
height: nodeRef.current?.offsetHeight || 0
});
}
};
const debouncedHandler = debounce(handleSize);
window.addEventListener("resize", debouncedHandler);
handleSize();
return () => window.removeEventListener("resize", debouncedHandler);
}, [node]);
return [setNode, size];
}
export default useElementSize;
我正在使用以下钩子来获取元素的宽度和高度:
import { useCallback, useLayoutEffect, useState } from "react";
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size,
] {
function debounce(func: Function) {
let timer: any;
return function (event: any) {
if (timer) clearTimeout(timer);
timer = setTimeout(func, 100, event);
};
}
// Mutable values like 'ref.current' aren't valid dependencies
// because mutating them doesn't re-render the component.
// Instead, we use a state as a ref to be reactive.
const [ref, setRef] = useState<T | null>(null);
const [size, setSize] = useState<Size>({
width: 0,
height: 0,
});
// Prevent too many rendering using useCallback
const handleSize = useCallback(() => {
if (ref?.offsetWidth && ref?.offsetHeight) {
setSize({
width: ref?.offsetWidth || 0,
height: ref?.offsetHeight || 0,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useLayoutEffect(() => {
window.addEventListener(
"resize",
debounce(function () {
handleSize();
}),
);
handleSize();
return () => window.removeEventListener("resize", handleSize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref?.offsetHeight, ref?.offsetWidth]);
return [setRef, size];
}
export default useElementSize;
它完美地工作 - 当我调整 window 的大小时,我发现每次我调整大小时组件都会重新呈现,而且它发生得非常快。
问题是我有一个非常大的元素,我想为 useElementHook 添加去抖动。我尝试添加 2 秒的去抖动,但出现以下行为:
这是我试过的:
import { useCallback, useLayoutEffect, useState } from "react";
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size,
] {
function debounce(func: Function) {
let timer: any;
return function (event: any) {
if (timer) clearTimeout(timer);
timer = setTimeout(func, 2000, event);
};
}
// Mutable values like 'ref.current' aren't valid dependencies
// because mutating them doesn't re-render the component.
// Instead, we use a state as a ref to be reactive.
const [ref, setRef] = useState<T | null>(null);
const [size, setSize] = useState<Size>({
width: 0,
height: 0,
});
// Prevent too many rendering using useCallback
const handleSize = useCallback(() => {
if (ref?.offsetWidth && ref?.offsetHeight) {
setSize({
width: ref?.offsetWidth || 0,
height: ref?.offsetHeight || 0,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useLayoutEffect(() => {
window.addEventListener(
"resize",
debounce(function () {
handleSize();
}),
);
handleSize();
return () => window.removeEventListener("resize", handleSize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref?.offsetHeight, ref?.offsetWidth]);
return [setRef, size];
}
export default useElementSize;
当前行为: 调整大小事件 -> 什么都不做 -> 2 秒过去了 -> 我看到 40 console.logs(组件仍然重新渲染 40 次而不是一次)。
我想要的: 调整大小事件 -> 什么都不做 -> 2 秒过去了 -> 1 console.log 并重新渲染组件一次!
有解决办法吗?
我已经对您的实现进行了一些更改以使其正常工作: 显着变化:
- 将
handleSize
移到效果内(不需要useCallback
- 使用对节点的引用以避免对效果的依赖
import { useLayoutEffect, useRef, useState } from "react";
interface Size {
width: number;
height: number;
}
function debounce(func: Function) {
let timer: any;
return function (event: any) {
if (timer) clearTimeout(timer);
timer = setTimeout(func, 2000, event);
};
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size
] {
const [node, setNode] = useState<T | null>(null);
const nodeRef = useRef(node);
const [size, setSize] = useState<Size>({
width: 0,
height: 0
});
useLayoutEffect(() => {
nodeRef.current = node;
}, [node]);
useLayoutEffect(() => {
const handleSize = () => {
if (nodeRef.current?.offsetWidth && nodeRef.current?.offsetHeight) {
setSize({
width: nodeRef.current?.offsetWidth || 0,
height: nodeRef.current?.offsetHeight || 0
});
}
};
const debouncedHandler = debounce(handleSize);
window.addEventListener("resize", debouncedHandler);
handleSize();
return () => window.removeEventListener("resize", debouncedHandler);
}, [node]);
return [setNode, size];
}
export default useElementSize;