我有一个应用程序,我需要动态设置元素的高度(让我们说“app-content”)。它需要应用程序的“chrome”的高度并减去它,然后将“app-content”的高度设置为在这些约束内适合100%。使用vanilla JS,jQuery或Backbone视图这是非常简单的,但我很难弄清楚在React中执行此操作的正确流程是什么?
以下是一个示例组件。我希望能够将app-content
的高度设置为窗口的100%减去ActionBar
和BalanceBar
的大小,但我怎么知道所有内容的呈现时间以及我会把计算内容放在这个React类中吗?
/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
render: function () {
return (
<div className="wrapper">
<Sidebar />
<div className="inner-wrapper">
<ActionBar title="Title Here" />
<BalanceBar balance={balance} />
<div className="app-content">
<List items={items} />
</div>
</div>
</div>
);
}
});
module.exports = AppBase;
答案 0 :(得分:253)
https://facebook.github.io/react/docs/react-component.html#componentdidmount
在渲染组件后调用此方法一次。所以你的代码看起来就是这样。
var AppBase = React.createClass({
componentDidMount: function() {
var $this = $(ReactDOM.findDOMNode(this));
// set el height and width etc.
},
render: function () {
return (
<div className="wrapper">
<Sidebar />
<div className="inner-wrapper">
<ActionBar title="Title Here" />
<BalanceBar balance={balance} />
<div className="app-content">
<List items={items} />
</div>
</div>
</div>
);
}
});
答案 1 :(得分:205)
使用componentDidUpdate
或componentDidMount
的一个缺点是它们实际上是在完成dom元素绘制之前执行的,但是在它们从React传递到浏览器后#39 ; s DOM。
比如说你需要将node.scrollHeight设置为渲染的node.scrollTop,那么React的DOM元素可能还不够。你需要等到元素完成后才能达到它们的高度。
解决方案:
使用requestAnimationFrame
确保在绘制新渲染的对象后运行代码
scrollElement: function() {
//store a this ref, and
var _this = this;
//wait for a paint to do scrolly stuff
window.requestAnimationFrame(function() {
var node = _this.getDOMNode();
if (node !== undefined) {
//and scroll them!
node.scrollTop = node.scrollHeight;
}
});
},
componentDidMount: function() {
this.scrollElement();
},
// and or
componentDidUpdate: function() {
this.scrollElement();
},
// and or
render: function() {
this.scrollElement()
return [...]
答案 2 :(得分:82)
根据我的经验window.requestAnimationFrame
并不足以确保DOM已从componentDidMount
完全呈现/重排完成。我运行的代码在componentDidMount
调用后立即访问DOM,并且仅使用window.requestAnimationFrame
将导致元素出现在DOM中;但是,由于尚未发生回流,因此对元素尺寸的更新尚未反映出来。
唯一真正可行的方法是将我的方法包装在setTimeout
和window.requestAnimationFrame
中,以确保React的当前调用堆栈在注册下一帧之前被清除&# 39; s渲染。
function onNextFrame(callback) {
setTimeout(function () {
window.requestAnimationFrame(callback)
}, 0)
}
如果我不得不推测为什么会发生这种情况/必要,我可以看到React批处理DOM更新,而不是实际将更改应用到DOM,直到当前堆栈完成。
最终,如果您在代码中使用DOM测量,那么在React回调之后,您可能会想要使用此方法。
答案 3 :(得分:7)
React的生命周期方法很少有助于这些情况,列表包括但不限于 getInitialState,getDefaultProps,componentWillMount,componentDidMount 等。
在您的情况下以及需要与DOM元素交互的情况下,您需要等到dom准备就绪,因此请使用 componentDidMount ,如下所示:
/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
componentDidMount: function() {
ReactDOM.findDOMNode(this).height = /* whatever HEIGHT */;
},
render: function () {
return (
<div className="wrapper">
<Sidebar />
<div className="inner-wrapper">
<ActionBar title="Title Here" />
<BalanceBar balance={balance} />
<div className="app-content">
<List items={items} />
</div>
</div>
</div>
);
}
});
module.exports = AppBase;
另外,有关生命周期反应的更多信息,请查看以下链接: https://facebook.github.io/react/docs/state-and-lifecycle.html
答案 4 :(得分:5)
我觉得这个解决方案很脏,但我们走了:
componentDidMount() {
this.componentDidUpdate()
}
componentDidUpdate() {
// A whole lotta functions here, fired after every render.
}
现在我要坐在这里等待下来的选票。
答案 5 :(得分:5)
您可以更改状态,然后在setState callback中进行计算。根据React文档,这“保证在应用更新后会触发”。
这应该在componentDidMount
或代码中的其他地方(例如在调整大小事件处理程序上)而不是在构造函数中完成。
这是window.requestAnimationFrame
的一个很好的选择,它没有某些用户在这里提到的问题(需要将其与setTimeout
结合使用或多次调用)。例如:
class AppBase extends React.Component {
state = {
showInProcess: false,
size: null
};
componentDidMount() {
this.setState({ showInProcess: true }, () => {
this.setState({
showInProcess: false,
size: this.calculateSize()
});
});
}
render() {
const appStyle = this.state.showInProcess ? { visibility: 'hidden' } : null;
return (
<div className="wrapper">
...
<div className="app-content" style={appStyle}>
<List items={items} />
</div>
...
</div>
);
}
}
答案 6 :(得分:3)
实际上有比使用请求animationframe或timeout更简单,更简洁的版本。 Iam对没有人提出来感到惊讶: vanilla-js onload处理程序。 如果可以,请使用组件安装,否则,只需在jsx组件的onload处理程序上绑定一个函数。如果要让函数运行每个渲染,则在返回结果之前,还要执行它。代码如下:
runAfterRender = () =>
{
const myElem = document.getElementById("myElem")
if(myElem)
{
//do important stuff
}
}
render()
{
this.runAfterRender()
return (
<div
onLoad = {this.runAfterRender}
>
//more stuff
</div>
)
}
}
答案 7 :(得分:3)
只需使用新的Hook方法来更新此问题,您只需使用useEffect
钩子即可:
import React, { useEffect } from 'react'
export default function App(props) {
useEffect(() => {
// your post layout code (or 'effect') here.
...
},
// array of variables that can trigger an update if they change. Pass an
// an empty array if you just want to run it once after component mounted.
[])
}
如果要在布局安装之前运行,请使用useLayoutEffect
钩子:
import React, { useLayoutEffect } from 'react'
export default function App(props) {
useLayoutEffect(() => {
// your pre layout code (or 'effect') here.
...
}, [])
}
答案 8 :(得分:2)
渲染后,您可以像下面一样指定高度,并可以指定相应反应组件的高度。
render: function () {
var style1 = {height: '100px'};
var style2 = { height: '100px'};
//window. height actually will get the height of the window.
var hght = $(window).height();
var style3 = {hght - (style1 + style2)} ;
return (
<div className="wrapper">
<Sidebar />
<div className="inner-wrapper">
<ActionBar style={style1} title="Title Here" />
<BalanceBar style={style2} balance={balance} />
<div className="app-content" style={style3}>
<List items={items} />
</div>
</div>
</div>
);`
}
或者您可以使用sass指定每个反应组件的高度。指定前2个具有固定宽度的反应分量主分区,然后使用auto指定第三个分量主分区的高度。因此,根据第三个div的内容,将分配高度。
答案 9 :(得分:2)
我实际上遇到了类似行为的问题,我在一个Component中使用它的id属性渲染一个视频元素,所以当RenderDOM.render()结束时,它会加载一个插件,需要id来找到占位符并且它无法找到它。
componentDidMount()中的0ms的setTimeout修复了它:)
componentDidMount() {
if (this.props.onDidMount instanceof Function) {
setTimeout(() => {
this.props.onDidMount();
}, 0);
}
}
答案 10 :(得分:1)
对我来说,window.requestAnimationFrame
或setTimeout
的任何组合都不会产生一致的结果。有时它起作用,但并非总是 - 或者有时候为时已晚。
我根据需要多次循环window.requestAnimationFrame
来修复它
(通常为0或2-3次)
关键是diff > 0
:在这里,我们可以确切确定网页何时更新。
// Ensure new image was loaded before scrolling
if (oldH > 0 && images.length > prevState.images.length) {
(function scroll() {
const newH = ref.scrollHeight;
const diff = newH - oldH;
if (diff > 0) {
const newPos = top + diff;
window.scrollTo(0, newPos);
} else {
window.requestAnimationFrame(scroll);
}
}());
}
答案 11 :(得分:1)
我遇到了同样的问题。
在my.fun<- function(band){
sprintf("%s<-data.frame(tetracam$filename,tetracam$time,tetracam$type,asd$%s,tetracam$%s)",band,band,band)
sprintf("names(%s)<-c('filename','time','type','asd','tetracam')",band)
sprintf("%s[order(%s$time),]",band,band)
}
中使用hack-ish setTimeout(() => { }, 0)
的大多数情况都有效。
但不是在特殊情况下;我并不想使用componentDidMount()
,因为文档说:
注意:findDOMNode是用于访问底层DOM的转义符号 节点。在大多数情况下,不鼓励使用这种逃生舱,因为 它刺穿了组件抽象。
(资料来源:https://facebook.github.io/react/docs/react-dom.html#finddomnode)
所以在那个特定组件中我必须使用ReachDOM findDOMNode
事件,所以我的代码最终是这样的:
componentDidUpdate()
然后:
componentDidMount() {
// feel this a little hacky? check this: http://stackoverflow.com/questions/26556436/react-after-render-code
setTimeout(() => {
window.addEventListener("resize", this.updateDimensions.bind(this));
this.updateDimensions();
}, 0);
}
最后,就我而言,我必须删除在componentDidUpdate() {
this.updateDimensions();
}
中创建的侦听器:
componentDidMount
答案 12 :(得分:1)
使用ES6
类而非React.createClass
import React, { Component } from 'react';
class SomeComponent extends Component {
constructor(props) {
super(props);
// this code might be called when there is no element avaliable in `document` yet (eg. initial render)
}
componentDidMount() {
// this code will be always called when component is mounted in browser DOM ('after render')
}
render() {
return (
<div className="component">
Some Content
</div>
);
}
}
另外 - 检查React组件生命周期方法:https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle
每个组件都有很多类似于componentDidMount
的方法,例如
componentWillUnmount()
- 即将从浏览器DOM中删除组件答案 13 :(得分:1)
来自ReactDOM.render()文档:
如果提供了可选的回调,它将在之后执行 组件被渲染或更新。
答案 14 :(得分:0)
当我需要打印接收大量数据并在画布上绘画的react组件时,我遇到了奇怪的情况。我已经尝试了所有提到的方法,但没有一种方法对我可靠地起作用,在setTimeout中使用requestAnimationFrame可以在20%的时间内获得空画布,所以我做了以下事情:
nRequest = n => range(0,n).reduce(
(acc,val) => () => requestAnimationFrame(acc), () => requestAnimationFrame(this.save)
);
基本上,我制作了一个requestAnimationFrame链,不确定是否是个好主意,但是到目前为止,这对我来说100%都有效(我使用30作为n变量的值)。
答案 15 :(得分:0)
我不会假装我知道为什么这个特定功能起作用,但是,当我需要使用 Ref <来访问DOM元素时, window.getComputedStyle 对我来说是100%的时间/ strong>中的 useEffect 中-我只能假定它也可以与 componentDidMount 一起使用。
我在 useEffect 中将其放在代码的顶部,并且出现,好像它迫使效果在继续绘制元素之前等待其被绘制下一行代码,但没有任何明显的延迟,例如使用 setTimeout 或异步睡眠功能。如果没有此设置,当我尝试访问Ref元素时,Ref元素将返回为 undefined 。
const ref = useRef(null);
useEffect(()=>{
window.getComputedStyle(ref.current);
// Next lines of code to get element and do something after getComputedStyle().
});
return(<div ref={ref}></div>);
答案 16 :(得分:0)
我目前正在使用钩子。
像这样:
import React, { useEffect } from 'react'
const AppBase = ({ }) => {
useEffect(() => {
// set el height and width etc.
}, [])
return (
<div className="wrapper">
<Sidebar />
<div className="inner-wrapper">
<ActionBar title="Title Here" />
<BalanceBar balance={balance} />
<div className="app-content">
<List items={items} />
</div>
</div>
</div>
);
}
export default AppBase
答案 17 :(得分:0)
对于功能组件,您可以react-use-call-onnext-render,它是一个自定义钩子,允许在以后的渲染中安排回调。
它在 one of my other projects 上成功使用。
要求 dom 元素的维度,
看这个例子,它是 react-use-call-onnext-render examples
上的第三个例子:
假设我们想要获取可移动 DOM 元素的尺寸,假设 div
由 showBox
状态控制
多变的。为此,我们可以使用 getBoundingClientRect()
。然而,我们只想在元素之后调用这个函数
安装到 dom 中,因此将在负责显示此元素的变量之后安排此调用一次渲染
在dom中发生了变化,这个变量是showBox
,所以他将成为useCallOnNextRender
的依赖:
const YourComponent = () => {
const [showBox, setShowBox] = useState(false)
const divRef = useRef()
const callOnNextShowBoxChange = useCallOnNextRender()
return (
<>
<div style={canvasStyle} id="canvas">
<button style={boxStyle} onClick={() => {
setShowBox(!showBox)
callOnNextShowBoxChange(() => console.log(divRef.current.getBoundingClientRect())) //right value
}}>toggle show box
</button>
<div style={{border: "black solid 1px"}} ref={divRef}>
{showBox ? <div style={boxStyle}>box2</div> : null}
</div>
</div>
</>
);
};
答案 18 :(得分:0)
在尝试了上述所有建议的解决方案后,我发现中间的一个元素具有CSS 过渡,这就是为什么在道具更改后我未能获得正确的计算几何图形的原因。
所以我不得不使用 onTransitionEnd
侦听器等待片刻尝试获取容器元素的 DOM 高度计算。
希望这可以节省某人的工作日,哈哈。
答案 19 :(得分:0)
对我来说,单独的 componentDidUpdate
或单独的 window.requestAnimationFrame
没有解决问题,但以下代码有效。
// Worked but not succinct
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.refreshFlag) { // in the setState for which you want to do post-rendering stuffs, set this refreshFlag to true at the same time, to enable this block of code.
window.requestAnimationFrame(() => {
this.setState({
refreshFlag: false // Set the refreshFlag back to false so this only runs once.
});
something = this.scatterChart.current.canvas
.toDataURL("image/png"); // Do something that need to be done after rendering is finished. In my case I retrieved the canvas image.
});
}
}
后来我测试了 requestAnimationFrame 评论,它仍然完美地工作:
// The best solution I found
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.refreshFlag) { // in the setState for which you want to do post-rendering stuffs, set this refreshFlag to true at the same time, to enable this block of code.
// window.requestAnimationFrame(() => {
this.setState({
refreshFlag: false // Set the refreshFlag back to false so this only runs once.
});
something = this.scatterChart.current.canvas
.toDataURL("image/png"); // Do something that need to be done after rendering is finished. In my case I retrieved the canvas image.
// });
}
}
我不确定额外的 setState
引起时间延迟是否只是巧合,因此在检索图像时,绘图已经完成(如果我删除了旧的画布图像,我将获得旧的画布图像setState
).
或者更可能是因为setState
需要在所有内容渲染完后才执行,所以强制等待渲染完成。
--我倾向于相信后者,因为根据我的经验,在我的代码中连续调用 setState
会导致每一个只在最后一次渲染完成后触发。
最后,我测试了以下代码。如果 this.setState({});
不更新组件,而是等到渲染完成,这将是最终的最佳解决方案,我想。然而,它失败了。即使传递空的 {}
,setState()
仍会更新组件。
// This one failed!
componentDidUpdate(prevProps, prevState, snapshot) {
// if (this.state.refreshFlag) {
// window.requestAnimationFrame(() => {
this.setState({});
something = this.scatterChart.current.canvas
.toDataURL("image/png");
// });
// }
}