React 渲染元素后如何获取元素的高度?
HTML
<div id="container">
<!-- This element's contents will be replaced with your component. -->
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
</div>
反应JS
var DivSize = React.createClass({
render: function() {
let elHeight = document.getElementById('container').clientHeight
return <div className="test">Size: <b>{elHeight}px</b> but it should be 18px after the render</div>;
}
});
ReactDOM.render(
<DivSize />,
document.getElementById('container')
);
结果
Size: 36px but it should be 18px after the render
它在渲染之前计算容器高度(36px)。我想在渲染后获得高度。在这种情况下,正确的结果应该是 18px。 jsfiddle
setState
将高度值分配给状态变量。
以下是使用 ref 的最新 ES6 示例。
请记住,我们必须使用 React class component,因为我们需要访问 Lifecycle 方法 componentDidMount()
,因为我们只能确定元素在 DOM 中呈现后的高度。
import React, {Component} from 'react'
import {render} from 'react-dom'
class DivSize extends Component {
constructor(props) {
super(props)
this.state = {
height: 0
}
}
componentDidMount() {
const height = this.divElement.clientHeight;
this.setState({ height });
}
render() {
return (
<div
className="test"
ref={ (divElement) => { this.divElement = divElement } }
>
Size: <b>{this.state.height}px</b> but it should be 18px after the render
</div>
)
}
}
render(<DivSize />, document.querySelector('#container'))
您可以在此处找到运行示例:https://codepen.io/bassgang/pen/povzjKw
对于那些对使用 react hooks
感兴趣的人,这可能会帮助您入门。
import React, { useState, useEffect, useRef } from 'react'
export default () => {
const [height, setHeight] = useState(0)
const ref = useRef(null)
useEffect(() => {
setHeight(ref.current.clientHeight)
})
return (
<div ref={ref}>
{height}
</div>
)
}
useLayoutEffect
而不是 useEffect
?文档似乎表明我们应该这样做。请参阅此处的“提示”部分:reactjs.org/docs/hooks-effect.html#detailed-explanation (2) 对于这些我们只需要引用子元素的情况,我们是否应该使用 useRef
而不是 createRef
?文档似乎表明 useRef
更适合此用例。请参阅:reactjs.org/docs/hooks-reference.html#useref
useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))
的尺寸,我的 IDE (WebStorm) 建议我这样做:ESLint: React Hook useEffect 包含对“setDimensions”的调用。如果没有依赖项列表,这可能会导致无限的更新链。要解决此问题,请将 [] 作为第二个参数传递给 useEffect Hook。 (react-hooks/exhaustive-deps),所以将 []
作为第二个参数传递给您的 useEffect
请参阅 this 小提琴(实际上已更新您的)
您需要连接到在渲染方法之后运行的 componentDidMount
。在那里,您可以获得元素的实际高度。
var DivSize = React.createClass({
getInitialState() {
return { state: 0 };
},
componentDidMount() {
const height = document.getElementById('container').clientHeight;
this.setState({ height });
},
render: function() {
return (
<div className="test">
Size: <b>{this.state.height}px</b> but it should be 18px after the render
</div>
);
}
});
ReactDOM.render(
<DivSize />,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
<!-- This element's contents will be replaced with your component. -->
</div>
除了使用 document.getElementById(...)
,一个更好的(最新的)解决方案是使用 React useRef 钩子来存储对组件/元素的引用,并结合 useEffect 钩子,它会在组件呈现时触发。
import React, {useState, useEffect, useRef} from 'react';
export default App = () => {
const [height, setHeight] = useState(0);
const elementRef = useRef(null);
useEffect(() => {
setHeight(elementRef.current.clientHeight);
}, []); //empty dependency array so it only runs once at render
return (
<div ref={elementRef}>
{height}
</div>
)
}
您还想在元素上使用 refs 而不是使用 document.getElementById
,这只是一个更强大的东西。
angularJs
和 react
怎么办?在某些情况下,确实需要直接的 DOM 操作
它可能显示为零。 setTimeout 有助于获取正确的值并更新状态。
import React, { useState, useEffect, useRef } from 'react'
export default () => {
const [height, setHeight] = useState(0)
const ref= useRef(null)
useEffect(() => {
if(elemRef.current.clientHeight){
setTimeout(() => {
setHeight(ref.current.clientHeight)
}, 1000)
}
})
return (
<div ref={ref}>
{height}
</div>
)
}
onreadystatechange
的解决方案,它也有效。我有一个 <canvas>
元素的引用,我试图获得正确的高度,setTimeout(()=>{}, 0)
和 document.onreadystatechange(()=>{})
都有助于获得正确的高度。
我的 2020 年(或 2019 年)答案
import React, {Component, useRef, useLayoutEffect} from 'react';
import { useDispatch } from 'react-redux';
import { Toast, ToastBody, ToastHeader } from 'reactstrap';
import {WidgetHead} from './WidgetHead';
export const Widget = ({title, toggle, reload, children, width, name}) => {
let myself = useRef(null);
const dispatch = useDispatch();
useLayoutEffect(()=>{
if (myself.current) {
const height = myself.current.clientHeight
dispatch({type:'GRID_WIDGET_HEIGHT', widget:name, height})
}
}, [myself.current, myself.current?myself.current.clientHeight:0])
return (
<Toast innerRef={myself}>
<WidgetHead title={title}
toggle={toggle}
reload={reload} />
<ToastBody>
{children}
</ToastBody>
</Toast>
)
}
让您对这里缺少的东西(WidgetHead)发挥您的想象力,您可以在 npm 上找到 reactstrap
:将 innerRef
替换为 ref
以获得旧版 dom 元素(例如 <div>
)。
useEffect 或 useLayoutEffect
最后一个据说是同步的变化
useLayoutEffect(或 useEffect)第二个参数
第二个参数是一个数组,在执行第一个参数中的函数之前对其进行检查。
我用了
[myself.current,myself.current?myself.current.clientHeight:0]
因为 myself.current 在渲染之前为空,这是一件好事,不要检查,最后的第二个参数 myself.current.clientHeight
是我要检查的更改。
我在这里解决什么(或试图解决)
我在这里解决了网格上的小部件的问题,它可以根据自己的意愿改变其高度,并且网格系统应该 弹性足够 以做出反应( https://github.com/STRML/react-grid-layout )。
useLayoutEffect
和实际的 div 将 ref 绑定到。
与钩子一起使用:
如果您的内容尺寸在加载后发生变化,这个答案会很有帮助。
onreadystatechange :当属于元素或 HTML 文档的数据的加载状态发生变化时发生。当页面内容的加载状态发生变化时,会在 HTML 文档上触发 onreadystatechange 事件。
import {useState, useEffect, useRef} from 'react';
const ref = useRef();
useEffect(() => {
document.onreadystatechange = () => {
console.log(ref.current.clientHeight);
};
}, []);
我试图使用嵌入的 youtube 视频播放器,其尺寸在加载后可能会发生变化。
setTimeout
的使用被低估了。在我看来,setTimeout(()=>{}, 0)
与 document.onreadstatechange = () => {}
具有相同的效果。无论哪种方式,在尝试将使用 Unity 构建的 webgl 游戏嵌入到网页时,我都需要使用其中之一来从 getBoundingRect()
、clientWidth
、offsetHeight
等中获取正确的宽度/高度
如果您需要窗口调整大小事件,这是另一个:
class DivSize extends React.Component {
constructor(props) {
super(props)
this.state = {
width: 0,
height: 0
}
this.resizeHandler = this.resizeHandler.bind(this);
}
resizeHandler() {
const width = this.divElement.clientWidth;
const height = this.divElement.clientHeight;
this.setState({ width, height });
}
componentDidMount() {
this.resizeHandler();
window.addEventListener('resize', this.resizeHandler);
}
componentWillUnmount(){
window.removeEventListener('resize', this.resizeHandler);
}
render() {
return (
<div
className="test"
ref={ (divElement) => { this.divElement = divElement } }
>
Size: widht: <b>{this.state.width}px</b>, height: <b>{this.state.height}px</b>
</div>
)
}
}
ReactDOM.render(<DivSize />, document.querySelector('#container'))
使用 useMeasure
作为自定义钩子(Typescript、SSR、钩子):
import { useEffect, useRef, useState } from 'react';
interface ContainerSize {
width: number;
height: number;
}
type UseMeasureArgs = () => {
ref: React.RefObject<HTMLDivElement>;
size: ContainerSize;
windowSize: ContainerSize;
};
const initSize: ContainerSize = { width: 0, height: 0 };
const useMeasure: UseMeasureArgs = () => {
const ref = useRef<HTMLDivElement>(null);
const [size, setSize] = useState<ContainerSize>(initSize);
const [windowSize, setWindowSize] = useState<ContainerSize>(initSize);
useEffect(() => {
if (ref.current) {
setSize({ width: ref.current.offsetWidth, height: ref.current.offsetHeight });
}
if (typeof window !== 'undefined') {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
}, []);
return { ref, size, windowSize };
};
export default useMeasure;
另一种解决方案是,如果您想同步检索 React 元素的大小而不必显式渲染该元素,您可以使用 ReactDOMServer 和 DOMParser。
在使用 react-window (react-virtualized) 时,我使用此函数来获取列表项渲染器的高度,而不必硬编码所需的 itemSize
道具FixedSizeList。
实用程序.js:
/**
* @description Common and reusable functions
*
* @requires react-dom/server
*
* @public
* @module
*
*/
import ReactDOMServer from "react-dom/server";
/**
* @description Retrieve the width and/or heigh of a React element without rendering and committing the element to the DOM.
*
* @param {object} elementJSX - The target React element written in JSX.
* @return {object}
* @public
* @function
*
* @example
*
* const { width, height } = getReactElementSize( <div style={{ width: "20px", height: "40px" }} ...props /> );
* console.log(`W: ${width}, H: ${height}); // W: 20, H: 40
*
*/
const getReactElementSize = (elementJSX) => {
const elementString = ReactDOMServer.renderToStaticMarkup(elementJSX);
const elementDocument = new DOMParser().parseFromString(elementString, "text/html");
const elementNode = elementDocument.getRootNode().body.firstChild;
const container = document.createElement("div");
const containerStyle = {
display: "block",
position: "absolute",
boxSizing: "border-box",
margin: "0",
padding: "0",
visibility: "hidden"
};
Object.assign(container.style, containerStyle);
container.appendChild(elementNode);
document.body.appendChild(container);
const width = container.clientWidth;
const height = container.clientHeight;
container.removeChild(elementNode);
document.body.removeChild(container);
return {
width,
height
};
};
/**
* Export module
*
*/
export {
getReactElementSize
};
我发现 React 钩子的其他答案在调整大小时没有正确更新。
在四处搜索后,我发现 this blog post 提供了一个可以观察调整大小事件的有效 React 钩子:
TL;DR 在这里:
npm install --save resize-observer-polyfill
// useResizeObserver.js
import { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import ResizeObserver from 'resize-observer-polyfill';
const useObserver = ({ callback, element }) => {
const current = element && element.current;
const observer = useRef(null);
useEffect(() => {
// if we are already observing old element
if (observer && observer.current && current) {
observer.current.unobserve(current);
}
const resizeObserverOrPolyfill = ResizeObserver;
observer.current = new resizeObserverOrPolyfill(callback);
observe();
return () => {
if (observer && observer.current && element &&
element.current) {
observer.current.unobserve(element.current);
}
};
}, [current]);
const observe = () => {
if (element && element.current && observer.current) {
observer.current.observe(element.current);
}
};
};
useObserver.propTypes = {
element: PropTypes.object,
callback: PropTypes.func,
};
export default useObserver;
然后是组件中的示例用法:
// shape.js
import React, { useEffect, useState, useRef } from 'react';
import useResizeObserver from 'path/to/useResizeObserver.js';
const Shape = () => {
const [height, setHeight] = useState(0);
const svgRef = useRef(null);
const doHeightAdjustment = () => {
setHeight(svgRef.current.clientHeight);
};
useResizeObserver({callback: doHeightAdjustment, element: svgRef});
return (
<div ref={svgRef} style={{ height: '100vh' }}>
{height}
</div>
);
};
export default Shape;
您还可以使用 getBoundingClientRect()
来获取高度、宽度。
const [width, setWidth] = useState(0);
useEffect(() => {
const element = document.getElementById('element-id');
if (element) {
setWidth(element.getBoundingClientRect().width); // or height
}
}, []);
我发现有用的 npm 包 https://www.npmjs.com/package/element-resize-detector
优化的跨浏览器调整元素大小监听器。
可以与 React 组件或功能组件一起使用(特别适用于 react hooks)
这是从 https://swizec.com/blog/usedimensions-a-react-hook-to-measure-dom-nodes 修改的一个很好的可重复使用的钩子:
import { useState, useCallback, useEffect } from 'react';
function getDimensionObject(node) {
const rect = node.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: 'x' in rect ? rect.x : rect.top,
left: 'y' in rect ? rect.y : rect.left,
x: 'x' in rect ? rect.x : rect.left,
y: 'y' in rect ? rect.y : rect.top,
right: rect.right,
bottom: rect.bottom
};
}
export function useDimensions(data = null, liveMeasure = true) {
const [dimensions, setDimensions] = useState({});
const [node, setNode] = useState(null);
const ref = useCallback(node => {
setNode(node);
}, []);
useEffect(() => {
if (node) {
const measure = () =>
window.requestAnimationFrame(() =>
setDimensions(getDimensionObject(node))
);
measure();
if (liveMeasure) {
window.addEventListener('resize', measure);
window.addEventListener('scroll', measure);
return () => {
window.removeEventListener('resize', measure);
window.removeEventListener('scroll', measure);
};
}
}
}, [node, data]);
return [ref, dimensions, node];
}
实施:
import { useDimensions } from '../hooks';
// Include data if you want updated dimensions based on a change.
const MyComponent = ({ data }) => {
const [
ref,
{ height, width, top, left, x, y, right, bottom }
] = useDimensions(data);
console.log({ height, width, top, left, x, y, right, bottom });
return (
<div ref={ref}>
{data.map(d => (
<h2>{d.title}</h2>
))}
</div>
);
};
不定期副业成功案例分享
<div ref={ divElement => this.divElement = divElement}>{children}</div>
抛出“[eslint] 箭头函数不应返回赋值。(no-return-assign)”和this.setState({ height });
抛出“[eslint] 不要在componentDidMount (react/no-did-mount-set-state)”。有人有符合样式指南的解决方案吗?ref={ divElement => {this.divElement = divElement}}
componentDidUpdate
。