反应"渲染后"代码?

Osc*_*son 329 javascript reactjs

我有一个应用程序,我需要动态设置元素的高度(让我们说"app-content").它需要应用程序的"chrome"的高度并减去它,然后将"app-content"的高度设置为在这些约束内适合100%.这对于vanilla JS,jQuery或Backbone视图来说非常简单,但是我很难弄清楚在React中执行此操作的正确流程是什么?

下面是一个示例组件.我希望能够设置app-content的高度是窗口减去的尺寸为100%ActionBarBalanceBar,但我怎么知道当一切都变得和我在哪里会把计算的东西,在这个阵营类?

/** @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;
Run Code Online (Sandbox Code Playgroud)

Har*_*fer 280

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>
    );
  }
});
Run Code Online (Sandbox Code Playgroud)

  • 或者`componentDidUpdate`,如果值可以在第一次渲染后更改. (206认同)
  • 在componentDidMount中更改它对于浏览器来说太快了.将它包装在一个setTimeout中,并没有给它实际的时间.即`componentDidMount:()=> {setTimeout(addClassFunction())}`,或使用rAF,下面的答案提供了这个答案. (11认同)
  • 谢谢.这个名字非常直观,我想知道为什么我会尝试像"init"或甚至"初始化"这样荒谬的名字. (8认同)
  • 我正在尝试更改设置为转换的css属性,以便在渲染后开始动画.不幸的是,更改`componentDidMount()`中的css不会导致转换. (5认同)
  • 这当然是行不通的。如果您得到一个节点列表,然后尝试遍历该节点列表,您会发现长度等于0。执行setTimeout并等待1秒钟对我有用。不幸的是,react并没有真正地等到呈现DOM之后才出现。 (2认同)

Gra*_*ath 223

使用的一个缺点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 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 [...]
Run Code Online (Sandbox Code Playgroud)

  • window.requestAnimationFrame对我来说还不够.我不得不用window.setTimeout破解它.Argggghhhhhh !!!!! (25认同)
  • @neptunian严格来说,"[RAF]在下次重绘之前被称为[...] ..." - [https://developer.mozilla.org/en-US/Apps/Fundamentals/Performance/CSS_JavaScript_animation_performance#requestAnimationFrame] .在这种情况下,节点仍然需要由DOM计算其布局(也称为"重排").这使用RAF作为从布局之前跳到布局之后的方式.来自Elm的浏览器文档是一个更好的地方:http://elmprogramming.com/virtual-dom.html#how-browsers-render-html (3认同)
  • `_this.getDOMNode 不是一个函数`这段代码到底是什么? (3认同)
  • 奇.也许它在最新版本的React中有所改变,我认为不需要调用requestAnimationFrame.文档说:"在将组件的更新刷新到DOM之后立即调用.不会为初始渲染调用此方法.使用此方法可以在组件更新时对DOM进行操作."...即,它被刷新,DOM节点应该存在. - https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate (2认同)
  • @JimSoho,我希望你是对的,这已经解决了,但该文档中实际上没有任何新内容。这是针对 dom 更新不够的边缘情况,我们等​​待绘制周期很重要。我试图用新版本和旧版本创建一个小提琴,但我似乎无法创建一个足够复杂的组件来演示这个问题,即使返回几个版本...... (2认同)

Ell*_*ong 92

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

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

function onNextFrame(callback) {
    setTimeout(function () {
        requestAnimationFrame(callback)
    })
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 通常 - 你是对的.但是,在React的componentDidMount方法的上下文中,如果在该堆栈完成之前附加requestAnimationFrame,则DOM实际上可能不会完全更新.我有代码在React的回调上下文中一致地重现这种行为.在更新DOM之后,确保代码正在执行的唯一方法(再次,在此特定的React用例中)是使用setTimeout首先清除调用堆栈. (7认同)
  • 您会注意到上面提到需要相同解决方法的其他评论,即:http://stackoverflow.com/questions/26556436/react-after-render-code/34999925#comment54662760_28748160这是此React唯一100%可靠的方法用例.如果我不得不猜测它可能是由于React批处理更新本身可能无法在当前堆栈中应用(因此将requestAnimationFrame推迟到下一帧以确保应用批处理). (6认同)
  • 我想你可能需要了解你的JS内部...... http://altitudelabs.com/blog/what-is-the-javascript-event-loop/ http://stackoverflow.com/questions/8058612/does -calling-的setTimeout清晰最调用堆栈 (4认同)
  • 等待当前的调用堆栈清除显然不是一个谈论事件循环的"荒谬"方式,但对于他自己的每一个我猜... (2认同)
  • 这作为嵌套的`requestAnimationFrame` 调用会更好吗?例如; `function onNextFrame(cb) { window.requestAnimationFrame(_ =&gt; window.requestAnimationFrame(cb)) }`。根据规范(https://html.spec.whatwg.org/multipage/webappapis.html#animation-frames),这将保证它在初始渲染后的下一帧上运行(特别是,查看订单执行“运行动画帧回调”中的列表)。它避免了下一帧何时执行 setTimeout 的歧义。 (2认同)

Jaa*_*rhu 8

我觉得这个解决方案很脏,但我们走了:

componentDidMount() {
    this.componentDidUpdate()
}

componentDidUpdate() {
    // A whole lotta functions here, fired after every render.
}
Run Code Online (Sandbox Code Playgroud)

现在我只是坐在这里等待下来的选票.

  • 而是从两个生命周期中调用方法,然后您不必从其他周期触发循环. (14认同)
  • 嗯,比喻为+1"坐在这里等待下来的选票".勇敢的人.; ^) (8认同)
  • 您应该尊重React组件生命周期. (5认同)
  • @TúbalMartín我知道。如果您有更好的方法达到相同的结果,请随时分享。 (2认同)
  • componentWillReceiveProps 应该这样做 (2认同)

Ali*_*eza 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;
Run Code Online (Sandbox Code Playgroud)

另外,有关生命周期反应的更多信息,请查看以下链接:https: //facebook.github.io/react/docs/state-and-lifecycle.html

getInitialState,getDefaultProps,componentWillMount,componentDidMount


Jos*_*238 8

您可以更改状态,然后在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>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)


P F*_*ter 6

只是使用新的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. 
     [])
}
Run Code Online (Sandbox Code Playgroud)

另外,如果要在布局安装之前运行,请使用useLayoutEffect钩子:

import React, { useLayoutEffect } from 'react'

export default function App(props) {

     useLayoutEffect(() => {
         // your pre layout code (or 'effect') here.
         ...
     }, [])
}
Run Code Online (Sandbox Code Playgroud)

  • 确实如此,但它确实在布局有机会绘制之前运行“在浏览器有机会绘制之前,useLayoutEffect 内计划的更新将同步刷新。”我将进行编辑。 (3认同)

小智 6

实际上,有比使用请求动画帧或超时更简单、更清晰的版本。我很惊讶没有人提出这个问题: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>
  )
}
Run Code Online (Sandbox Code Playgroud)

}


ron*_*ron 5

我遇到了同样的问题。

在大多数情况下,使用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);
}
Run Code Online (Sandbox Code Playgroud)

然后:

componentDidUpdate() {
    this.updateDimensions();
}
Run Code Online (Sandbox Code Playgroud)

最后,就我而言,我必须删除在中创建的侦听器componentDidMount

componentWillUnmount() {
    window.removeEventListener("resize", this.updateDimensions.bind(this));
}
Run Code Online (Sandbox Code Playgroud)