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

下面是一个示例组件。我想要能够设置应用内容的高度为窗口的100%减去动作栏和BalanceBar的大小,但我怎么知道什么时候所有的渲染和哪里我将把计算的东西在这个反应类?

/** @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;

当前回答

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

在我的另一个项目中成功地使用了它。

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

假设我们想要获取一个可移动DOM元素的尺寸,假设是由showBox状态控制的div 变量。为此,我们可以使用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>
        </>
    );
};

其他回答

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

componentDidMount() {
    this.componentDidUpdate()
}

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

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

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

在我的另一个项目中成功地使用了它。

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

假设我们想要获取一个可移动DOM元素的尺寸,假设是由showBox状态控制的div 变量。为此,我们可以使用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>
        </>
    );
};

对我来说,componentDidUpdate单独或窗口。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");
            // });
        // }
    }

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

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

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

只是用新的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.
         ...
     }, [])
}