“渲染”后反应;代码?

我有一个应用程序,我需要动态设置一个元素的高度(让我们说“app-content”)。它取应用程序的“chrome”的高度并减去它,然后设置“app-content”的高度以100%符合这些限制。这是超级简单的香草JS, jQuery,或骨干视图,但我努力弄清楚正确的过程将在React中这样做?

下面是一个示例组件。我希望能够将app-content的高度设置为窗口的100%减去ActionBarBalanceBar的大小,但我如何知道什么时候所有的东西都被渲染,以及在这个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;
448627 次浏览

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>
);
}
});

使用componentDidUpdatecomponentDidMount的一个缺点是,它们实际上是在dom元素完成绘制之前执行的,而是在它们从React传递到浏览器的dom之后。

比如说,如果你需要set node。到呈现节点的scrollHeight。scrollTop,那么React的DOM元素可能不够。你需要等到元素被画完才能得到它们的高度。

解决方案:

使用requestAnimationFrame来确保您的代码在绘制新呈现的对象之后运行

scrollElement: function() {
// Store a 'this' ref, and
var _this = this;
// wait for a paint before running scrollHeight dependent code.
window.requestAnimationFrame(function() {
var node = _this.getDOMNode();
if (node !== undefined) {
node.scrollTop = node.scrollHeight;
}
});
},
componentDidMount: function() {
this.scrollElement();
},
// and or
componentDidUpdate: function() {
this.scrollElement();
},
// and or
render: function() {
this.scrollElement()
return [...]

ReactDOM.render ()文档中:

如果提供了可选的回调,它将在 组件被渲染或更新

根据我的经验,window.requestAnimationFrame不足以确保DOM已从componentDidMount完全呈现/回流完成。我运行的代码在componentDidMount调用后立即访问DOM,仅使用window.requestAnimationFrame将导致元素出现在DOM中;然而,对元素尺寸的更新还没有反映出来,因为回流还没有发生。

唯一真正可靠的工作方式是将我的方法包装在setTimeoutwindow.requestAnimationFrame中,以确保React的当前调用堆栈在注册下一帧渲染之前被清除。

function onNextFrame(callback) {
setTimeout(function () {
requestAnimationFrame(callback)
})
}

如果我不得不推测为什么这是发生的/必要的,我可以看到React批处理DOM更新,直到当前堆栈完成后才实际应用更改到DOM。

最终,如果你在React回调后触发的代码中使用DOM度量,你可能会想要使用这个方法。

渲染之后,你可以像下面这样指定高度,并可以为相应的react组件指定高度。

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指定每个react组件的高度。指定前2个react组件主div的固定宽度,然后第三个组件主div的高度为auto。因此,根据第三个div的内容,高度将被分配。

我实际上遇到了类似行为的麻烦,我在一个组件中渲染了一个视频元素,它的id属性,所以当RenderDOM.render()结束时,它加载了一个需要id来找到占位符的插件,但它找不到它。

componentDidMount()中的setTimeout与0ms修复了它:)

componentDidMount() {
if (this.props.onDidMount instanceof Function) {
setTimeout(() => {
this.props.onDidMount();
}, 0);
}
}

我觉得这个解决方案很脏,但我们开始吧:

componentDidMount() {
this.componentDidUpdate()
}


componentDidUpdate() {
// A whole lotta functions here, fired after every render.
}

现在我就坐在这里,等着大家投反对票。

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组件生命周期方法:组件生命周期

每个组件都有很多类似componentDidMount的方法。

  • componentWillUnmount() -组件即将从浏览器DOM中删除

我也遇到了同样的问题。

在大多数情况下,在componentDidMount()中使用hack-ish setTimeout(() => { }, 0)是有效的。

但不是在特殊情况下;我不想使用ReachDOM findDOMNode,因为文档说:

注意:findDOMNode是一个用于访问底层DOM的escape hatch 节点。在大多数情况下,不鼓励使用这个逃生口,因为

(来源: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中创建的侦听器:

componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions.bind(this));
}

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;
更多关于react生命周期的信息,你可以看看下面的链接: https://facebook.github.io/react/docs/state-and-lifecycle.html < / p >

getInitialState, getDefaultProps, componentWillMount, componentDidMount .

对我来说,没有window.requestAnimationFramesetTimeout的组合产生一致的结果。有时它会起作用,但并不总是如此——或者有时为时已晚。

我在必要的时候通过循环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);
}
}());
}

你可以改变状态,然后在设置状态回调中进行计算。根据React文档,这是“保证在更新应用后触发”。

这应该在componentDidMount或代码中的其他地方(如在resize事件处理程序上)完成,而不是在构造函数中。

这是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>
);
}
}

当我需要打印接收大量数据的react组件并在画布上绘制时,我遇到了奇怪的情况。我已经尝试了所有提到的方法,它们都不可靠地为我工作,与requestAnimationFrame内setTimeout我得到空画布在20%的时间,所以我做了以下工作:

nRequest = n => range(0,n).reduce(
(acc,val) => () => requestAnimationFrame(acc), () => requestAnimationFrame(this.save)
);

基本上我做了一个requestAnimationFrame的链,不确定这是好主意还是不好,但这在100%的情况下为我到目前为止(我使用30作为n变量的值)。

只是用新的钩子方法来更新一下这个问题,你可以简单地使用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.
...
}, [])
}
实际上,有一个比使用request animationframe或timeouts更简单、更清晰的版本。我很惊讶居然没人提起这件事: vanilla-js的onload处理程序。 如果可以,使用component did mount,如果不行,只需在jsx组件的onload处理程序上绑定一个函数。如果你想让函数运行每次渲染,也要在返回渲染函数的结果之前执行它。代码看起来像这样:

runAfterRender = () =>
{
const myElem = document.getElementById("myElem")
if(myElem)
{
//do important stuff
}
}


render()
{
this.runAfterRender()
return (
<div
onLoad = {this.runAfterRender}
>
//more stuff
</div>
)
}

我不会假装我知道为什么这个特定的函数可以工作,但是当我需要在useEffect中使用裁判访问DOM元素时,window.getComputedStyle可以100%工作-我只能假设它也可以与componentDidMount一起工作。

我把它放在useEffect中代码的顶部,它出现,就好像它强迫效果等待元素被绘制,然后再继续下一行代码,但没有任何明显的延迟,比如使用setTimeout或异步睡眠函数。如果不这样做,当我试图访问Ref元素时,它将返回未定义的

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>);

我建议你使用钩子。

你可以在官方反应的文档中检查这个钩子的行为。

就像这样:

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

对于功能性组件,你可以react-use-call-onnext-render,它是一个自定义钩子,允许在以后的渲染中调度回调。

它在我的另一个项目上被成功使用。

要求dom元素的维度, 请看这个例子,它是react-use-call-onnext-render examples上的第三个例子:

假设我们想要获取一个可移动DOM元素的尺寸,假设divshowBox状态控制 变量。为此,我们可以使用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>
</>
);
};
在尝试了上面所有建议的解决方案后,没有运气,我发现我的一个元素在中间有CSS转换,这就是为什么我在道具改变后未能得到正确的计算几何。 所以我不得不使用onTransitionEnd监听器来等待片刻,当尝试获得容器元素的DOM高度计算时。

对我来说,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");
// });
// }
}