我在 Chrome 上使用 React 15,想要连接一个事件监听器来检测父容器的更改。在四处寻找选项后,我遇到了 ResizeObserver,但不知道如何让它在我的项目中工作。
目前,我将其放入构造函数中,但它似乎没有打印任何文本,而且我不确定要在
observe
调用中放入什么内容。
class MyComponent extends React.Component {
constructor(props) {
super(props);
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(somethingGoesHere);
}
render() {
return (
<AnotherComponent>
<YetAnotherComponent>
</YetAnotherComponent>
<CanYouBelieveIt>
</CanYouBelieveIt>
<RealComponent />
</AnotherComponent>
);
}
}
理想情况下,我也不想将
RealComponent
包装在 div
中并给它 div
一个 id。有办法直接去RealComponent
吗?
我的目标是观察
RealComponent
的任何调整大小变化,但 MyComponent
也很好。我应该在somethingGoesHere
槽里放什么?
编辑:
为了让某些东西发挥作用,我硬着头皮在
div
周围包裹了一个 RealComponent
标签。然后我给了它一个 id <div id="myDivTag">
并更改了 observe
调用:
resizeObserver.observe(document.getElementById("myDivTag"));
但是,当运行这个时,我得到:
未捕获类型错误:resizeObserver.observe 不是函数
任何帮助将不胜感激。
ComponentDidMount
将是设置观察者的最佳位置,但您也想在 ComponentWillUnmount
上断开连接。
class MyComponent extends React.Component {
resizeObserver = null;
resizeElement = createRef();
componentDidMount() {
this.resizeObserver = new ResizeObserver((entries) => {
// do things
});
this.resizeObserver.observe(this.resizeElement.current);
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
render() {
return (
<div ref={this.resizeElement}>
...
</div>
);
}
}
编辑:下面Davidicus的答案更完整,先看那里
ResizeObserver 无法进入构造函数,因为此时 div 在组件生命周期中还不存在。
我认为你无法绕过额外的 div,因为无论如何 React 组件都会减少为 html 元素。
将其放入 componentDidMount 中,它应该可以工作:
componentDidMount() {
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(document.getElementById("myDivTag"));
}
我最近遇到了类似的问题,不同之处在于我的应用程序主要使用钩子和功能组件。
这是一个如何在 React 功能组件中使用 ResizeObserver 的示例(在打字稿中):
const resizeObserver = React.useRef<ResizeObserver>(new ResizeObserver((entries:ResizeObserverEntry[]) => {
// your code to handle the size change
}));
const resizedContainerRef = React.useCallback((container: HTMLDivElement) => {
if (container !== null) {
resizeObserver.current.observe(container);
}
// When element is unmounted, ref callback is called with a null argument
// => best time to cleanup the observer
else {
if (resizeObserver.current)
resizeObserver.current.disconnect();
}
}, [resizeObserver.current]);
return <div ref={resizedContainerRef}>
// Your component content here
</div>;
import { useLayoutEffect, useRef, useState } from 'react';
import { shallow } from 'zustand/shallow';
import { debounce } from '@/common/utils';
export interface ObservableElementSize {
scrollWidth: number;
offsetWidth: number;
scrollHeight: number;
offsetHeight: number;
}
export interface useResizeObserverConfig {
lazy?: boolean;
debounceTimeout?: number;
}
export const useResizeObserver = <T extends HTMLElement>(config?: useResizeObserverConfig) => {
const [size, setSize] = useState<ObservableElementSize | null>();
const elementRef = useRef<T | null>(null);
const lazy = config?.lazy ?? false;
const debounceTimeout = config?.debounceTimeout ?? 200;
useLayoutEffect(() => {
if(!elementRef.current) {
return;
}
// initialize value
setSize((prevState) => {
const nextState = {
scrollWidth: elementRef.current!.scrollWidth,
offsetWidth: elementRef.current!.offsetWidth,
scrollHeight: elementRef.current!.scrollHeight,
offsetHeight: elementRef.current!.offsetHeight,
};
if(shallow(prevState, nextState)) {
return prevState;
}
return nextState;
});
if(lazy) {
return;
}
const debouncedSetSize = debounce(setSize, debounceTimeout);
const handleSaveNodeSize = (node?: T | null) => {
if(!node) {
return;
}
debouncedSetSize((prevState) => {
const nextState = {
scrollWidth: node.scrollWidth,
offsetWidth: node.offsetWidth,
scrollHeight: node.scrollHeight,
offsetHeight: node.offsetHeight,
};
if(shallow(prevState, nextState)) {
return prevState;
}
return nextState;
});
};
const onResize = (entries: ResizeObserverEntry[]) => {
const [entry] = entries;
handleSaveNodeSize(entry?.target as T);
};
const resizeObserverRef = new ResizeObserver(onResize);
resizeObserverRef.observe(elementRef.current);
return () => {
resizeObserverRef.disconnect();
debouncedSetSize.cancel();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef.current, debounceTimeout, lazy]);
return {
elementRef,
size,
};
};
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}