反应“渲染后”代码?


367

我有一个应用程序,需要动态设置元素的高度(让我们说“ app-content”)。它获取应用程序“ chrome”的高度,然后减去它的高度,然后将“ app-content”的高度设置为在这些限制内适合100%。这对于原始的JS,jQuery或Backbone视图来说非常简单,但是我正在努力弄清楚在React中执行此操作的正确过程是什么?

下面是一个示例组件。我希望能够将app-content的高度设置为窗口的100%减去ActionBarand 的大小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;

4
一开始我就像“ flexbox如何解决这个问题?” 然后我想起了Flexbox中的列功能,它就像一个魅力一样起作用!
奥斯卡·戈德森

Answers:


301

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

213
或者componentDidUpdate在第一次渲染后值可以更改。
zackify

5
我正在尝试更改设置为过渡的css属性,以便动画在渲染后开始。不幸的是,更改CSS componentDidMount()不会引起过渡。
eye_mew 2014年

8
谢谢。该名称是如此直观,以至于我为什么要尝试使用“ init”甚至“ initialize”之类的荒谬名称。
Pawel 2015年

13
对于浏览器来说,在componentDidMount中更改它太快了。将其包装在setTimeout中,不提供实际时间。即componentDidMount: () => { setTimeout(addClassFunction())},或使用rAF,下面的答案将提供此答案。

3
这当然是行不通的。如果您得到一个节点列表,然后尝试遍历该节点列表,您会发现长度等于0。执行setTimeout并等待1秒钟对我有用。不幸的是,react并没有真正地等到呈现DOM之后才出现。
NickJ

241

使用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 [...]

29
window.requestAnimationFrame对我来说还不够。我不得不用window.setTimeout来破解它。Argggghhhhhh !!!!!
亚历克斯(Alex)

2
奇。也许它在最新版本的React中已更改,我认为对requestAnimationFrame的调用不是必需的。文档说:“将组件的更新刷新到DOM后立即调用。初始渲染不调用此方法。将其用作在组件更新后在DOM上进行操作的机会。”刷新后,应该存在DOM节点。- facebook.github.io/react/docs/...
吉姆·苏荷

1
@JimSoho,我希望你是对的,这个问题已解决,但是该文档中实际上没有任何新内容。这是针对dom不足以更新的边缘情况,因此我们必须等待绘画周期很重要。我试图用新版本和旧版本来创建小提琴,但是我似乎无法创建足够复杂的组件来演示该问题,甚至回溯了几个版本……
Graham P Heath 2016年

3
@neptunian严格来说,“ [RAF]在下一次重涂之前被称为...。”-[ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/… ]。在这种情况下,节点仍然需要通过DOM计算其布局(也称为“重排”)。这使用RAF作为从布局之前跳到布局之后的一种方式。Elm的浏览器文档提供了更多的好地方:elmprogramming.com/virtual-dom.html#how-browsers-render-html
Graham P Heath

1
_this.getDOMNode is not a function这是什么代码?
OZZIE

104

以我的经验window.requestAnimationFrame,还不足以确保DOM已从完全渲染/重排完整componentDidMount。我运行的代码在componentDidMount调用后立即访问DOM,并且仅使用它window.requestAnimationFrame会导致DOM中存在该元素;但是,由于尚未发生回流,因此尚未反映出元素尺寸的更新。

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

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

如果我不得不猜测为什么会发生这种情况/有必要,我可以看到React批处理DOM更新,并且直到当前堆栈完成后才真正将更改应用于DOM。

最终,如果您在代码中使用DOM测量,则在React回调之后触发,您可能需要使用此方法。


1
您只需要setTimeout或requestAnimationFrame,而无需两者都需要。

7
通常-您是正确的。但是,在React的componentDidMount方法的上下文中,如果在该堆栈完成之前附加了requestAnimationFrame,则DOM可能实际上并未完全更新。我有代码可以在React的回调上下文中一致地重现此行为。确保您的代码在DOM更新之后正在执行(在此特定的React用例中再次执行)的唯一方法是首先使用setTimeout清除调用堆栈。
Elliot Chong

6
您会注意到上面提到的其他注释需要相同的解决方法,即:stackoverflow.com/questions/26556436/react-after-render-code / ... 这是此React用例的唯一100%可靠的方法。如果我不得不猜测的话,那可能是由于React批处理更新本身导致的,可能不会在当前堆栈中应用(因此将requestAnimationFrame推迟到下一帧以确保已应用批处理)。
Elliot Chong


2
等待当前的调用堆栈清除显然不是谈论事件循环的“荒谬”方式,而是我想每个人……
Elliot Chong

17

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

根据React的文档,useLayoutEffect 所有DOM突变之后发生reactjs.org/docs/hooks-reference.html#uselayouteffect
Philippe Hebert

是的,但是它确实会在版面有机会绘画之前进行运行Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
P福斯特

您是否偶然知道useEffect是否在浏览器重排后运行(不是React所说的“画图”)?使用useEffect请求元素的scrollHeight是否安全?
eMontielG

使用起来很安全效果
P Fuster

13

您可以更改状态,然后在setState回调中进行计算。根据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>
        );
    }
}

1
这是我最喜欢的答案。干净且良好的惯用React代码。
phatmann

1
这是一个很好的答案!谢谢!
Bryan Jyh Herng Chong

1
这很漂亮,支持此评论!
bingeScripter

11

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

componentDidMount() {
    this.componentDidUpdate()
}

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

现在,我将坐在这里等待投票否决。


5
您应该尊重React组件的生命周期。
塔布尔·马丁

2
@TúbalMartín我知道。如果您有更好的方法达到相同的结果,请随时分享。
Jaakko Karhu

8
嗯,象征性的+1是“坐在这里,等待降票”。勇敢的人。; ^)
ruffin

14
而是从两个生命周期中调用一个方法,那么您不必从其他生命周期中触发生命周期。
Tjorriemorrie

1
componentWillReceiveProps应该执行此操作
Pablo

8

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

getInitialState,getDefaultProps,componentWillMount,componentDidMount


我的组件确实在页面呈现之前进行了挂载运行,由于api调用加载了数据,导致了很大的延迟。
詹森·G,

6

我遇到了同样的问题。

在大多数情况下,使用hack-ish是可行setTimeout(() => { }, 0)componentDidMount()

但不是特殊情况;而且我不想使用,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));
}

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个具有固定宽度的react组件主div的高度,然后指定auto的第三个组件主div的高度。因此,将根据第三个div的内容指定高度。


2

我实际上在处理类似行为时遇到了麻烦,我在具有其id属性的Component中渲染了一个视频元素,因此当RenderDOM.render()结束时,它会加载一个需要ID来查找占位符的插件,但找不到它。

在componentDidMount()内部具有0ms的setTimeout修复了它:)

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

1

ReactDOM.render()文档中:

如果提供了可选的回调,它将在呈现或更新组件之后执行。


9
您可以添加一个如何使用此示例吗?我主要从render方法返回元素,我不调用render并提供值。
dcsan 2015年

23
不幸的是,您提到的回调仅可用于顶级ReactDOM.render,而不适用于组件级别ReactElement.render(此处是主题)。
Bramus

1
这里的示例会有所帮助
DanV

1
我单击了您答案中的链接,但找不到您所引用的行,并且您的答案中没有足够的信息来进行操作。请访问stackoverflow.com/help/how-to-answer以获得有关如何编写一个好问题的建议
Benubird

1

对我而言,没有任何结合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);
        }
    }());
}

0

当我需要打印接收大量数据并在画布上绘画的react组件时,我遇到了奇怪的情况。我已经尝试了所有提到的方法,但没有一种方法对我可靠地起作用,在setTimeout中使用requestAnimationFrame可以在20%的时间内获得空画布,因此我做了以下工作:

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

基本上,我做了一个requestAnimationFrame的链,不确定是否是个好主意,但是到目前为止,这对我来说100%都有效(我使用30作为n变量的值)。


0

实际上,有比使用请求animationframe或timeout更简单,更简洁的版本。Iam惊讶的是没有人提出来:vanilla-js onload处理程序。如果可以的话,使用组件安装,否则,只需在jsx组件的onload hanlder上绑定一个函数。如果要让函数运行每个渲染,则在返回结果之前,还要执行它。代码如下所示:

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

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

}


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

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

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

1
毫不恭敬,但这如何回答问题?显示ES6的更新与问题并没有任何关系,也没有任何改变。所有更老的答案都已经在谈论componentDidMount如何无法单独工作。
dave4jr
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.