我有一个应用程序,我需要动态设置元素的高度(比如说“应用程序内容”)。它取应用程序“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;
这个方法在你的组件被渲染后被调用一次。所以你的代码看起来像这样。
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>
);
}
});
使用 componentDidUpdate
或 componentDidMount
的一个缺点是它们实际上是在 dom 元素绘制完成之前执行的,但是在它们从 React 传递到浏览器的 DOM 之后。
例如,如果您需要将 node.scrollHeight 设置为渲染的 node.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 [...]
_this.getDOMNode is not a function
这段代码到底是什么?
根据我的经验,window.requestAnimationFrame
不足以确保 DOM 已从 componentDidMount
完全呈现/回流完成。我运行的代码在 componentDidMount
调用后立即访问 DOM,仅使用 window.requestAnimationFrame
会导致元素出现在 DOM 中;但是,由于尚未发生回流,因此尚未反映对元素尺寸的更新。
唯一真正可靠的方法是将我的方法包装在 setTimeout
和 window.requestAnimationFrame
中,以确保在注册下一帧的渲染之前清除 React 的当前调用堆栈。
function onNextFrame(callback) {
setTimeout(function () {
requestAnimationFrame(callback)
})
}
如果我不得不推测为什么会发生这种情况/这是必要的,我可以看到 React 批处理 DOM 更新,并且直到当前堆栈完成后才真正将更改应用到 DOM。
最终,如果您在 React 回调后触发的代码中使用 DOM 测量值,您可能希望使用此方法。
requestAnimationFrame
调用会更好吗?例如; function onNextFrame(cb) { window.requestAnimationFrame(_ => window.requestAnimationFrame(cb)) }
。根据规范 (html.spec.whatwg.org/multipage/webappapis.html#animation-frames),这将保证它在初始渲染后的下一帧上运行(特别是在“运行动画帧回调”中检查执行列表的顺序)。它避免了在下一帧何时执行 setTimeout 的歧义。
只是为了用新的 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.
...
}, [])
}
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
我将进行编辑。
您可以更改状态,然后在 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>
);
}
}
我觉得这个解决方案很脏,但我们开始吧:
componentDidMount() {
this.componentDidUpdate()
}
componentDidUpdate() {
// A whole lotta functions here, fired after every render.
}
现在我只是坐在这里等待否决票。
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
https://i.stack.imgur.com/g9wys.png
我遇到了同样的问题。
在大多数情况下,在 componentDidMount()
中使用 hack-ish setTimeout(() => { }, 0)
都有效。
但不是在特殊情况下;而且我不想使用 ReachDOM findDOMNode
,因为文档说:
注意:findDOMNode 是一个用于访问底层 DOM 节点的逃生舱口。在大多数情况下,不鼓励使用此逃生舱口,因为它会穿透组件抽象。
(来源: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));
}
实际上有比使用请求动画帧或超时更简单和更干净的版本。我很惊讶没有人提出它:vanilla-js onload 处理程序。如果可以,使用组件确实挂载,如果没有,只需在 jsx 组件的 onload 处理程序上绑定一个函数。如果您希望该函数运行每个渲染,请在将结果返回渲染函数之前执行它。代码如下所示:
runAfterRender = () => { const myElem = document.getElementById("myElem") if(myElem) { //做重要的事情 } } render() { this.runAfterRender() return (
}
onLoad = {this.runAfterRender()}
即调用函数。
this.runAfterRender()
调用。 onLoad={this.runAfterRender}
应该是 onLoad={this.runAfterRender()}
。这确实会在加载时触发该功能。
我实际上遇到了类似行为的问题,我在一个带有 id 属性的组件中渲染了一个视频元素,所以当 RenderDOM.render() 结束时,它会加载一个需要 id 来查找占位符的插件,但它找不到它。
componentDidMount() 内的 0ms 的 setTimeout 修复了它:)
componentDidMount() {
if (this.props.onDidMount instanceof Function) {
setTimeout(() => {
this.props.onDidMount();
}, 0);
}
}
渲染后,您可以像下面这样指定高度,并且可以指定相应反应组件的高度。
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 个反应组件主 div,然后用 auto 指定第三个组件主 div 的高度。因此,将根据第三个 div 的内容分配高度。
对我来说,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);
}
}());
}
当我需要打印接收大量数据并在画布上绘制的反应组件时,我遇到了奇怪的情况。我已经尝试了所有提到的方法,但没有一个对我来说可靠,在 setTimeout 中使用 requestAnimationFrame 我有 20% 的时间得到空画布,所以我做了以下操作:
nRequest = n => range(0,n).reduce(
(acc,val) => () => requestAnimationFrame(acc), () => requestAnimationFrame(this.save)
);
基本上我做了一个 requestAnimationFrame 链,不确定这是不是个好主意,但到目前为止,这对我来说 100% 的情况下都有效(我使用 30 作为 n 变量的值)。
我不会假装我知道为什么这个特定的功能有效,但是 window.getComputedStyle 100% 的时间对我有效,只要我需要在 useEffect 中使用 Ref 访问 DOM 元素——我只能假设它可以与 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>);
对于功能组件,您可以react-use-call-onnext-render,它是一个自定义挂钩,允许在以后的渲染中安排回调。
它在 one of my other projects 上成功使用。
对于需要 dom 元素的维度,请参见此示例,它是 react-use-call-onnext-render examples
上的第三个示例:
假设我们想要获取可移动 DOM 元素的尺寸,假设 div 由 showBox 状态变量控制。为此,我们可以使用 getBoundingClientRect()。但是,我们只想在元素挂载到dom之后调用这个函数,所以会在dom中负责显示这个元素的变量发生变化后调度这个调用一次渲染,这个变量是showBox,所以他将依赖使用CallOnNextRender:
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");
// });
// }
}
我建议你使用钩子。它们从 16.8.0 版本开始可用。
您可以在官方的 react documentation 中检查此钩子的行为。
像这样的东西:
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
来自 ReactDOM.render() 文档:
如果提供了可选的回调,它将在组件渲染或更新后执行。
ReactDOM.render
,而不适用于 component level's ReactElement.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 组件生命周期方法:The Component Lifecycle
每个组件都有很多类似于 componentDidMount
的方法,例如。
componentWillUnmount() - 组件即将从浏览器 DOM 中移除
不定期副业成功案例分享
componentDidUpdate
如果值在第一次渲染后可以更改。componentDidMount()
中的 css 不会导致转换。componentDidMount: () => { setTimeout(addClassFunction())}
,或使用rAF,下面的答案提供了这个答案。