ChatGPT解决这个技术问题 Extra ChatGPT

反应“渲染后”代码?

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

下面是一个示例组件。我希望能够将 app-content 的高度设置为窗口的 100% 减去 ActionBarBalanceBar 的大小,但是我如何知道所有内容何时呈现以及我将计算内容放在哪里在这个 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;

S
Silicum Silium

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

componentDidUpdate 如果值在第一次渲染后可以更改。
我正在尝试更改设置为过渡的 css 属性,以便动画在渲染后开始。不幸的是,更改 componentDidMount() 中的 css 不会导致转换。
谢谢。这个名字非常直观,我想知道为什么我要尝试像“init”甚至“initialize”这样荒谬的名字。
在 componentDidMount 中更改它对于浏览器来说太快了。将它包装在一个 setTimeout 中并且不给它实际时间。即componentDidMount: () => { setTimeout(addClassFunction())},或使用rAF,下面的答案提供了这个答案。
这肯定行不通。如果你得到一个节点列表,然后尝试遍历节点列表,你会发现长度等于 0。执行 setTimeout 并等待 1 秒对我有用。不幸的是,react 似乎没有一个真正等到 DOM 被渲染后的方法。
G
Graham P Heath

使用 componentDidUpdatecomponentDidMount 的一个缺点是它们实际上是在 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 [...]

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

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

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

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

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

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


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

只是为了用新的 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
没错,但它会在布局有机会绘制之前运行 Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.我将进行编辑。
您是否碰巧知道 useEffect 是否在浏览器的重排之后运行(而不是 React 所谓的“绘制”)?使用 useEffect 请求元素的 scrollHeight 是否安全?
可以放心使用效果
是的,从类中重构我的组件并使用 useEffect 对我有用
J
Joseph238

您可以更改状态,然后在 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>
        );
    }
}

这是我最喜欢的答案。干净和良好的惯用 React 代码。
这是一个很好的答案!谢谢!
J
Jaakko Karhu

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

componentDidMount() {
    this.componentDidUpdate()
}

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

现在我只是坐在这里等待否决票。


你应该尊重 React 组件的生命周期。
@TúbalMartín 我知道。如果您有更好的方法来达到相同的结果,请随时分享。
嗯,“坐在这里等待反对票”的比喻 +1。勇敢的人。 ;^)
而是从两个生命周期中调用一个方法,然后您不必从其他周期触发周期。
componentWillReceiveProps 应该这样做
A
Alireza

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


我的组件确实在页面呈现之前挂载运行,导致在数据中加载 api 调用时出现很大延迟。
S
Silicum Silium

我遇到了同样的问题。

在大多数情况下,在 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));
}

F
Farantir

实际上有比使用请求动画帧或超时更简单和更干净的版本。我很惊讶没有人提出它:vanilla-js onload 处理程序。如果可以,使用组件确实挂载,如果没有,只需在 jsx 组件的 onload 处理程序上绑定一个函数。如果您希望该函数运行每个渲染,请在将结果返回渲染函数之前执行它。代码如下所示:

runAfterRender = () => { const myElem = document.getElementById("myElem") if(myElem) { //做重要的事情 } } render() { this.runAfterRender() return (

//更多的东西
) }

}


非常感谢!代码中的错字?应该是 onLoad = {this.runAfterRender()} 即调用函数。
我认为您可以在 then render() 函数开始时删除 this.runAfterRender() 调用。 onLoad={this.runAfterRender} 应该是 onLoad={this.runAfterRender()}。这确实会在加载时触发该功能。
这确实有效!
在 React 17 中不适合我
B
Barceyken

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

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

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

H
H. Pauwelyn

渲染后,您可以像下面这样指定高度,并且可以指定相应反应组件的高度。

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 的内容分配高度。


j
jackcogdill

对我来说,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);
        }
    }());
}

A
Anatoly Strashkevich

当我需要打印接收大量数据并在画布上绘制的反应组件时,我遇到了奇怪的情况。我已经尝试了所有提到的方法,但没有一个对我来说可靠,在 setTimeout 中使用 requestAnimationFrame 我有 20% 的时间得到空画布,所以我做了以下操作:

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

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


T
Total Legend

我不会假装我知道为什么这个特定的功能有效,但是 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>);

E
Eliav Louski

对于功能组件,您可以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>
        </>
    );
};

Y
Yuri Gor

在尝试了上面所有建议的解决方案后,我发现中间的一个元素有 CSS 过渡,这就是为什么我在道具更改后未能获得正确的计算几何。所以我不得不使用 onTransitionEnd 侦听器等待片刻,以尝试获取容器元素的 DOM 高度计算。希望这将节省某人的工作日哈哈。


Y
Yan Yang

对我来说,单独使用 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");
            // });
        // }
    }

X
Xabi

我建议你使用钩子。它们从 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

M
Mark Amery

来自 ReactDOM.render() 文档:

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


你能添加一个如何使用它的例子吗?我主要从渲染方法返回元素,我不调用渲染并提供值。
不幸的是,您提到的回调仅适用于 the toplevel ReactDOM.render,而不适用于 component level's ReactElement.render(这是这里的主题)。
这里的例子会有所帮助
我单击了您答案中的链接,但找不到您引用的行,并且您的答案不包含足够的信息,没有它就可以使用。有关如何写出好问题的建议,请参阅 stackoverflow.com/help/how-to-answer
S
Silicum Silium

使用 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 中移除


没有不尊重,但这如何回答这个问题?显示 ES6 的更新与问题无关/不会改变任何东西。所有更早的答案都已经讨论了 componentDidMount 如何独立工作。