如何在 React 中使用 Resize Observer
在Web APIs 系列Resize Observer快速指南的第一部分中,我们了解了 Resize Observer API 是什么以及如何将它与 vanilla JavaScript 一起使用。但是在使用 React 时该怎么办?
今天我们将看看如何快速简单地完成它,并将创建一个自定义钩子,您可以在您的项目中使用它。
API
让我们重复一下我们已经知道的:
-
ResizeObserver 用于观察Element大小的变化,
-
创建我们自己的观察者实例,我们调用
ResizeObserver构造函数,传递回调函数,每次大小变化时都会触发该函数:
const myObserver = new ResizeObserver(
(entries: ResizeObserverEntry[], observer: ResizeObserver) => {
for (let entry of entries) {
// Do something with an entry (see in next section)
}
});
进入全屏模式 退出全屏模式
- 到 start / stop 观察元素的大小,我们将调用
observe/unobserve实例的方法:
const myElement = document.getElementById('my-element');
myObserver.observe(myElement); // Start watching
myObserver.unobserve(myElement); // Stop watching
进入全屏模式 退出全屏模式
- 每个观察到的条目包含条目的元素大小信息:
interface ResizeObserverEntry {
readonly target: Element;
readonly borderBoxSize: ReadonlyArray<ResizeObserverSize>;
readonly contentBoxSize: ReadonlyArray<ResizeObserverSize>;
readonly devicePixelContentBoxSize: ReadonlyArray<ResizeObserverSize>;
readonly contentRect: DOMRectReadOnly; // May be deprecated, don't use it!
}
interface ResizeObserverSize {
readonly blockSize: number;
readonly inlineSize: number;
}
进入全屏模式 退出全屏模式
任务
接下来,我们想利用我们的知识来获取任何 React 应用程序的大小。没有比创建 React Hook 更好的解决方案了,它可以在任何组件的所有项目中使用。
因此,让我们尝试从钩子中定义我们想要的内容:
1.应该是_universal_,用法必须像const size = giveMeMySize()一样_simple_;
-
正如您可能在上一节中看到的(我希望😄),一个
ResizeObserver实例能够处理任意数量的元素。如果我们想保持我们的应用程序性能,钩子应该只在里面使用_single观察者实例_; -
为了方便使用钩子,它应该_处理突变_本身并自动_停止观察卸载_;
-
请记住,尽管
ResizeObserver API已经得到广泛支持,但它仍处于编辑草稿中,并非所有浏览器都支持。最好为它提供一个_fallback_。
解决方案
我们的要求看起来不错而且相当严格,嗯?但别担心,我们可以使用 Jared Lunde 的漂亮react-hook库中的漂亮且非常易于使用的useResizeObserver挂钩来处理所有这些问题。根据它的文档和我的测试和使用,它也满足我们的所有要求:
- 使用单个
ResizeObserver来跟踪挂钩使用的所有元素。这种方法的性能比大多数钩子实现使用每个元素ResizeObserver的性能要好得多,
- 当当前浏览器不支持
ResizeObserver时,使用@juggle/resize-observer作为 ponyfill,
- 当钩子卸载时自动不观察目标元素,
- 您不必将回调包装在
useCallback()中,因为任何突变都由钩子处理。
感觉很有希望,为什么我们现在不使用它呢?
钩子
我们终于准备好创建我们的钩子了。首先,安装useResizeObserver钩子:
npm install @react-hook/resize-observer
// Or
yarn install @react-hook/resize-observer
进入全屏模式 退出全屏模式
然后让我们定义我们的钩子的样子:
// useElementSize.ts
import { MutableRefObject, useLayoutEffect, useRef, useState } from 'react';
import useResizeObserver from '@react-hook/resize-observer';
interface Size {
width: number;
height: number;
}
export default function useElementSize<T extends HTMLElement = HTMLDivElement>(): [MutableRefObject<T | null>, Size] {
const target = useRef<T | null>(null);
const [size, setSize] = useState<Size>({
width: 0,
height: 0,
});
return [target, size]
}
进入全屏模式 退出全屏模式
如您所见,我们创建了useElementSize函数,我们可以提供一个通用类型的元素。它创建target引用以连接到组件内的 React 元素,并创建size用于当前元素的大小状态,它实现了Size接口。
请记住,当 Element 正在调整大小时,它的尺寸可以(并且通常是)十进制数字。我们当然可以对它们进行四舍五入:
const setRoundedSize = ({ width, height }: Size) => {
setSize({ width: Math.round(width), height: Math.round(height) });
};
进入全屏模式 退出全屏模式
接下来,我们需要设置Element的初始size。这就是 ReactuseLayoutEffect钩子非常适合的地方。它在浏览器绘制之前触发,允许我们使用它的getBoundingClientRect方法获取元素的尺寸:
useLayoutEffect(() => {
target.current && setRoundedSize(target.current.getBoundingClientRect())
}, [target]);
进入全屏模式 退出全屏模式
最后但同样重要的是,让我们在useResizeObserver钩子的帮助下放一些魔法(不是),这将在每次目标大小发生变化时触发size更新:
useResizeObserver(target, entry => {
const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0];
setRoundedSize({ width, height });
});
进入全屏模式 退出全屏模式
以及用法🤠
让我们尝试重复从第一部分快速指南到 Resize Observer系列的练习,让我提醒您任务:
假设我们有一盒草莓,让它们变大让我们非常高兴(反之亦然)。
我不会详细解释,因为您还记得,我们的目标是创建一个非常易于使用的钩子。您可以在下面检查代码以及它是如何一起工作的。
你觉得结果如何?
希望您喜欢本指南,请继续关注。
更多推荐

所有评论(0)