react“渲染后”代码?

IT技术 javascript reactjs
2021-03-15 22:24:47

我有一个应用程序,我需要在其中动态设置元素的高度(比如“app-content”)。它获取应用程序“chrome”的高度并减去它,然后将“应用程序内容”的高度设置为 100% 适合这些约束。这对于 vanilla 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;
6个回答

组件DidMount()

此方法在您的组件呈现后调用一次。所以你的代码看起来像这样。

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>
    );
  }
});
我正在尝试更改设置为过渡的 css 属性,以便动画在渲染后开始。不幸的是,更改 csscomponentDidMount()并不会导致转换。
2021-04-21 22:24:47
在 componentDidMount 中更改它对于浏览器来说太快了。将它包装在一个 setTimeout 中并且不给它实际时间。componentDidMount: () => { setTimeout(addClassFunction())},或使用 rAF,下面的答案提供了这个答案。
2021-04-29 22:24:47
谢谢。这个名字太直观了,我想知道为什么我要尝试像“init”甚至“initialize”这样荒谬的名字。
2021-05-01 22:24:47
或者componentDidUpdate值是否可以在第一次渲染后改变。
2021-05-07 22:24:47
这肯定不起作用。如果您获得一个节点列表,然后尝试遍历该节点列表,您会发现长度等于 0。执行 setTimeout 并等待 1 秒对我有用。不幸的是,React 似乎没有真正等到 DOM 呈现之后的方法。
2021-05-13 22:24:47

使用componentDidUpdate, or 的一个缺点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 [...]
奇怪的。也许它在最新版本的 React 中发生了变化,我认为没有必要调用 requestAnimationFrame。文档说:“在组件的更新刷新到 DOM 后立即调用。初始渲染不会调用此方法。当组件更新时,将此作为对 DOM 进行操作的机会。” ...即,它被刷新,DOM 节点应该存在。-- facebook.github.io/react/docs/...
2021-04-20 22:24:47
@neptunian 严格来说“[RAF] 在下一次重绘之前被称为 [...]...”——[ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/... ]。在这种情况下,节点仍然需要由 DOM 计算其布局(又名“回流”)。这使用 RAF 作为从布局之前跳转到布局之后的一种方式。Elm 的浏览器文档是了解更多信息的好地方:elmprogramming.com/virtual-dom.html#how-browsers-render-html
2021-04-20 22:24:47
_this.getDOMNode is not a function 这段代码是什么鬼?
2021-04-21 22:24:47
window.requestAnimationFrame 对我来说还不够。我不得不用 window.setTimeout 破解它。啊啊啊啊啊啊啊!!!!!
2021-04-26 22:24:47
@JimSoho,我希望你是对的,这已经解决了,但该文档中实际上没有任何新内容。这是针对 dom 更新不够的边缘情况,我们等​​待绘制周期很重要。我试图用新版本和旧版本创建一个小提琴,但我似乎无法创建一个足够复杂的组件来演示这个问题,即使返回几个版本......
2021-05-10 22:24:47

根据我的经验window.requestAnimationFrame,这不足以确保 DOM 已从componentDidMount. 我运行的代码在componentDidMount调用后立即访问 DOM ,单独使用window.requestAnimationFrame会导致元素出现在 DOM 中;但是,由于尚未发生回流,因此尚未反映对元素尺寸的更新。

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

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

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

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

您会注意到上面提到的其他评论需要相同的解决方法,即:stackoverflow.com/questions/26556436/react-after-render-code/... 这是此 React 用例唯一 100% 可靠的方法。如果我不得不冒险猜测它可能是由于 React 批处理更新本身可能不会在当前堆栈中应用(因此将 requestAnimationFrame 推迟到下一帧以确保应用批处理)。
2021-05-05 22:24:47
通常 - 你是对的。但是,在 React 的 componentDidMount 方法的上下文中,如果在堆栈完成之前附加 requestAnimationFrame DOM 实际上可能不会完全更新。我有一些代码可以在 React 回调的上下文中一致地重现这种行为。在 DOM 更新后确保您的代码正在执行的唯一方法(再次,在这个特定的 React 用例中)是首先使用 setTimeout 清除调用堆栈。
2021-05-07 22:24:47
这作为嵌套requestAnimationFrame调用会更好吗?例如; function onNextFrame(cb) { window.requestAnimationFrame(_ => window.requestAnimationFrame(cb)) }. 根据规范(html.spec.whatwg.org/multipage/webappapis.html#animation-frames),这将保证它在初始渲染后的下一帧上运行(特别是,检查执行列表的顺序在“运行动画帧回调”中)。它避免了下一帧何时执行 setTimeout 的歧义。
2021-05-10 22:24:47
您只需要 setTimeout 或 requestAnimationFrame,而不是两者。
2021-05-11 22:24:47
2021-05-12 22:24:47

只是用新的 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.我将进行编辑。
2021-04-17 22:24:47
根据 React 的文档,useLayoutEffect所有 DOM 突变之后发生reactjs.org/docs/hooks-reference.html#uselayouteffect
2021-04-18 22:24:47
是的,从类重构我的组件并使用 useEffect 对我有用
2021-04-19 22:24:47
你是否知道 useEffect 是否在浏览器回流后运行(不是 React 所谓的“paint”)?使用 useEffect 请求元素的 scrollHeight 是否安全?
2021-04-29 22:24:47
可以安全使用效果
2021-04-29 22:24:47

您可以更改状态,然后在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>
        );
    }
}
这是一个很好的答案!谢谢!
2021-05-03 22:24:47
这是我最喜欢的答案。干净和良好的惯​​用 React 代码。
2021-05-06 22:24:47