我有一个react组件,它需要在呈现自身之前提前知道它的尺寸。
当我在 jquery 中创建一个小部件时,我可以$('#container').width()
在构建组件时提前获取容器的宽度。
<div id='container'></div>
这些容器的尺寸在 CSS 中定义,以及页面上的一堆其他容器。谁定义了 React 中组件的高度和宽度以及位置?我习惯于 CSS 这样做并且能够访问它。但是在 React 中,我似乎只能在组件呈现后才能访问该信息。
我有一个react组件,它需要在呈现自身之前提前知道它的尺寸。
当我在 jquery 中创建一个小部件时,我可以$('#container').width()
在构建组件时提前获取容器的宽度。
<div id='container'></div>
这些容器的尺寸在 CSS 中定义,以及页面上的一堆其他容器。谁定义了 React 中组件的高度和宽度以及位置?我习惯于 CSS 这样做并且能够访问它。但是在 React 中,我似乎只能在组件呈现后才能访问该信息。
下面的示例使用 react 钩子useEffect。
工作示例在这里
import React, { useRef, useLayoutEffect, useState } from "react";
const ComponentWithDimensions = props => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({ width:0, height: 0 });
useLayoutEffect(() => {
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}, []);
return (
<div ref={targetRef}>
<p>{dimensions.width}</p>
<p>{dimensions.height}</p>
</div>
);
};
export default ComponentWithDimensions;
useEffect将无法检测到它自己对宽度和高度的影响
例如,如果您在不指定初始值(例如const [dimensions, setDimensions] = useState({});
)的情况下更改状态挂钩,则渲染时高度将读取为零,因为
在大多数用例中,这可能不是问题,但我想我会包含它,因为它对窗口大小调整有影响。
我还认为原始问题中有一些未探索的含义。我遇到了为动态绘制的组件(例如图表)调整窗口大小的问题。
即使没有指定,我也包括这个答案,因为
- 可以公平地假设,如果应用程序需要尺寸,则在调整窗口大小时可能需要它们。
- 只有 state 或 props 的变化才会导致重绘,因此还需要一个窗口大小调整监听器来监控维度的变化
- 如果在每个具有更复杂组件的窗口调整大小事件上重绘组件,则会影响性能。我发现引入 setTimeout 和 clearInterval 有帮助。我的组件包含一个图表,所以我的 CPU 飙升,浏览器开始爬行。下面的解决方案为我解决了这个问题。
下面的代码,这里的工作示例
import React, { useRef, useLayoutEffect, useState } from 'react';
const ComponentWithDimensions = (props) => {
const targetRef = useRef();
const [dimensions, setDimensions] = useState({});
// holds the timer for setTimeout and clearInterval
let movement_timer = null;
// the number of ms the window size must stay the same size before the
// dimension state variable is reset
const RESET_TIMEOUT = 100;
const test_dimensions = () => {
// For some reason targetRef.current.getBoundingClientRect was not available
// I found this worked for me, but unfortunately I can't find the
// documentation to explain this experience
if (targetRef.current) {
setDimensions({
width: targetRef.current.offsetWidth,
height: targetRef.current.offsetHeight
});
}
}
// This sets the dimensions on the first render
useLayoutEffect(() => {
test_dimensions();
}, []);
// every time the window is resized, the timer is cleared and set again
// the net effect is the component will only reset after the window size
// is at rest for the duration set in RESET_TIMEOUT. This prevents rapid
// redrawing of the component for more complex components such as charts
window.addEventListener('resize', ()=>{
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});
return (
<div ref={ targetRef }>
<p>{ dimensions.width }</p>
<p>{ dimensions.height }</p>
</div>
);
}
export default ComponentWithDimensions;
re:窗口大小调整超时- 在我的例子中,我正在绘制一个带有这些值下游图表的仪表板,我发现 100 毫秒RESET_TIMEOUT
似乎在 CPU 使用率和响应能力之间取得了很好的平衡。我没有关于什么是理想的客观数据,所以我把它变成了一个变量。
正如已经提到的,在渲染到 DOM 之前,您无法获得任何元素的尺寸。你可以在 React 中做的是只渲染一个容器元素,然后获取它的大小componentDidMount
,然后渲染其余的内容。
我做了一个工作示例。
请注意,使用setState
incomponentDidMount
是一种反模式,但在这种情况下很好,因为这正是我们想要实现的目标。
干杯!
代码:
import React, { Component } from 'react';
export default class Example extends Component {
state = {
dimensions: null,
};
componentDidMount() {
this.setState({
dimensions: {
width: this.container.offsetWidth,
height: this.container.offsetHeight,
},
});
}
renderContent() {
const { dimensions } = this.state;
return (
<div>
width: {dimensions.width}
<br />
height: {dimensions.height}
</div>
);
}
render() {
const { dimensions } = this.state;
return (
<div className="Hello" ref={el => (this.container = el)}>
{dimensions && this.renderContent()}
</div>
);
}
}
你不能。反正不可靠。这通常是浏览器行为的限制,而不是 React。
当您调用 时$('#container').width()
,您正在查询已在 DOM 中呈现的元素的宽度。即使在 jQuery 中,你也无法解决这个问题。
如果在渲染之前绝对需要元素的宽度,则需要估计它。如果您需要在可见之前进行测量,您可以在应用时进行visibility: hidden
,或者在页面上离散地渲染它,然后在测量后移动它。
@shane 处理窗口大小调整的方法有一个意想不到的“问题”:功能组件在每次重新渲染时添加一个新的事件侦听器,并且永远不会删除事件侦听器,因此事件侦听器的数量随着每次调整大小而呈指数增长。您可以通过记录对 window.addEventListener 的每次调用来查看:
window.addEventListener("resize", () => {
console.log(`Resize: ${dimensions.width} x ${dimensions.height}`);
clearInterval(movement_timer);
movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});
这可以通过使用事件清理模式来解决。这里有一些代码混合了@shane 的代码和本教程,在自定义钩子中具有调整大小的逻辑:
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect, useLayoutEffect, useRef } from "react";
// Usage
function App() {
const targetRef = useRef();
const size = useDimensions(targetRef);
return (
<div ref={targetRef}>
<p>{size.width}</p>
<p>{size.height}</p>
</div>
);
}
// Hook
function useDimensions(targetRef) {
const getDimensions = () => {
return {
width: targetRef.current ? targetRef.current.offsetWidth : 0,
height: targetRef.current ? targetRef.current.offsetHeight : 0
};
};
const [dimensions, setDimensions] = useState(getDimensions);
const handleResize = () => {
setDimensions(getDimensions());
};
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useLayoutEffect(() => {
handleResize();
}, []);
return dimensions;
}
export default App;
有一个工作示例这里。
为简单起见,此代码不使用计时器,但在链接教程中进一步讨论了该方法。
如前所述,这是浏览器的一个限制——它们在操作 DOM 的脚本和事件处理程序执行之间一次性和“在一个线程中”(从 JS 的角度)呈现。要在操作/加载 DOM 后获取尺寸,您需要让出(离开您的函数)并让浏览器渲染,并对渲染完成的某些事件做出react。
但是试试这个技巧:
您可以尝试设置 CSSdisplay: hidden; position: absolute;
并将其限制为一些不可见的边界框以获得所需的宽度。然后yield,当渲染完成后,调用$('#container').width()
.
这个想法是:由于display: hidden
使元素占据可见的空间,因此计算必须在后台完成。我不确定这是否符合“渲染前”的条件。
免责声明:
我还没有尝试过,所以请告诉我它是否有效。
而且我不确定它会如何与 React 融合。