「レンダリング後」のコードに反応しますか?


367

要素の高さを動的に設定する必要があるアプリ(「app-content」としましょう)があります。アプリの「クローム」の高さを取り、それを差し引いて、「app-content」の高さをこれらの制約内で100%に収まるように設定します。これはバニラJS、jQuery、またはバックボーンビューで非常にシンプルですが、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の列機能を思い出しました。
Oscar Godson 2014年

回答:


301

componentDidMount()

このメソッドは、コンポーネントがレンダリングされた後に1回呼び出されます。したがって、コードは次のようになります。

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

214
またはcomponentDidUpdate、最初のレンダリング後に値が変化する可能性がある場合。
zackify 2014年

5
アニメーションがレンダリング後に開始されるように、transitionに設定されているcssプロパティを変更しようとしています。残念ながら、CSSを変更してcomponentDidMount()も移行は行われません。
eye_mew 14年

8
ありがとう。名前は非常に直感的で、なぜ「init」や「initialize」のようなばかげた名前を付けようとしたのかと思います。
Pawel

13
componentDidMountでの変更は、ブラウザにとって速すぎます。それをsetTimeoutでラップし、実際の時間を与えないでください。つまりcomponentDidMount: () => { setTimeout(addClassFunction())}、またはrAFを使用すると、この下の答えがこの答えを提供します。

3
これは間違いなく機能しません。ノードリストを取得してから、ノードリストを反復処理しようとすると、長さが0に等しいことがわかります。setTimeoutを実行し、1秒待つとうまくいきました。残念ながら、それは反応しないようですが、DOMがレンダリングされるまで本当に待機するメソッドがあります。
NickJ

241

使用しての欠点の1つcomponentDidUpdate、またはcomponentDidMountDOM要素が描画されて行われる前に、彼らは、ブラウザの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 !!!!!
アレックス、

2
奇数。Reactの最新バージョンで変更されている可能性があります。requestAnimationFrameの呼び出しは必要ないと思います。ドキュメントは、「コンポーネントの更新がDOMにフラッシュされた直後に呼び出されます。このメソッドは、最初のレンダリングでは呼び出されません。これは、コンポーネントが更新されたときにDOMを操作する機会として使用してください。」...すなわち、フラッシュされると、DOMノードが存在するはずです。- facebook.github.io/react/docs/...
ジム・ソーホー

1
@JimSoho、これが修正されたことは正しいと思いますが、そのドキュメントには実際には何も新しいものはありません。これは、更新されるdomが十分ではないエッジケース用であり、ペイントサイクルを待つことが重要です。新しいバージョンと古いバージョンでフィドルを作成しようとしましたが、問題を実証するのに十分なほど複雑なコンポーネントを作成できなかったようです。いくつかのバージョンをさかのぼってみました...
Graham P Heath

3
@neptunian厳密に言えば「[RAF]は次の再描画の前に[...]と呼ばれます...」-[ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/… ]。この状況でも、ノードはDOMによってレイアウトを計算する必要があります(別名「リフロー」)。これは、レイアウトの前からレイアウトの後にジャンプする方法としてRAFを使用します。Elmのブラウザドキュメントは、次のような場合に適しています。elmprogramming.com
Graham P Heath

1
_this.getDOMNode is not a functionこのコードは一体何ですか?
OZZIE

104

私の経験でwindow.requestAnimationFrameは、DOMがから完全にレンダリング/リフロー完了したことを確認するのに十分ではありませんでしたcomponentDidMountcomponentDidMount呼び出し直後にDOMにアクセスするコードを実行していて、単独で使用window.requestAnimationFrameすると、要素がDOMに存在することになります。ただし、リフローがまだ行われていないため、要素の寸法の更新はまだ反映されていません。

これが機能する唯一の信頼できる方法は、私のメソッドをa setTimeoutとa でラップしてwindow.requestAnimationFrame、次のフレームのレンダーに登録する前にReactの現在のコールスタックが確実にクリアされるようにすることです。

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

なぜこれが起こっている/必要なのかを推測する必要がある場合、現在のスタックが完了するまで、ReactがDOMの更新をバッチ処理し、実際にDOMに変更を適用しないことがわかります。

最終的に、Reactコールバックの後に起動するコードでDOM測定を使用している場合は、おそらくこのメソッドを使用する必要があります。


1
setTimeoutまたはrequestAnimationFrameのみが必要で、両方は必要ありません。

7
通常、あなたは正しいです。ただし、ReactのcomponentDidMountメソッドのコンテキストでは、スタックが完了する前にrequestAnimationFrameをアタッチすると、DOMが実際に完全に更新されない場合があります。Reactのコールバックのコンテキスト内でこの動作を一貫して再現するコードがあります。DOMが更新された後、コードが確実に実行される唯一の方法(この特定のReactユースケースでもう一度)は、setTimeoutを使用して最初にコールスタックをクリアすることです。
Elliot Chong、2016

6
上記の同じ回避策が必要であるという他のコメントに気づくでしょう。つまり、stackoverflow.com / questions / 26556436 / react-after-render-code / これは、このReactユースケースで唯一の100%信頼できる方法です。私が推測をしなければならなかった場合、それは現在のスタック内で適用されない可能性のあるReactバッチ更新自体が原因である可能性があります(したがって、バッチが確実に適用されるようにrequestAnimationFrameを次のフレームに延期します)。
Elliot Chong

4
私はあなたのJS内部...をブラッシュアップする必要があるかもしれないと思うaltitudelabs.com/blog/what-is-the-javascript-event-loop stackoverflow.com/questions/8058612/...
エリオット・チョン

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 Fuster

ブラウザーのリフロー後にReactが「ペイント」と呼ぶものではなく、useEffectが実行されるかどうかを知っていますか?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コード。
ファットマン

1
これは素晴らしい答えです!ありがとう!
ブライアンジーヘルチョン

1
これは美しいです。このコメントに賛成投票してください!
bingeScripter

11

私はこの解決策は汚いと感じていますが、ここに行きます:

componentDidMount() {
    this.componentDidUpdate()
}

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

今、私はただここに座って、反対票を待つつもりです。


5
Reactコンポーネントのライフサイクルを尊重する必要があります。
卵管マルティン

2
@TúbalMartín知ってる。同じ結果に到達するためのより良い方法がある場合は、自由に共有してください。
Jaakko Karhu、2016

8
ええと、「ここに座って反対票を待つ」という比喩的な+1。勇者。; ^)
ruffin

14
両方のライフサイクルからメソッドを呼び出すのではなく、他のサイクルからサイクルをトリガーする必要はありません。
Tjorriemorrie 2017年

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;

また、reactのライフサイクルの詳細については、以下のリンクを参照してください。https//facebook.github.io/react/docs/state-and-lifecycle.html

getInitialState、getDefaultProps、componentWillMount、componentDidMount


API呼び出しがデータに読み込まれると、ページがレンダリングされる前に私のコンポーネントがマウント実行され、大きな遅延が発生しました。
Jason G

6

私も同じ問題に遭遇しました。

ほとんどのシナリオでハックっぽいを使用して動作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つの反応コンポーネントのメインdivを固定幅で指定し、次に3番目のコンポーネントのメインdivの高さを自動で指定します。したがって、3番目のdivのコンテンツに基づいて、高さが割り当てられます。


2

実際に同様の動作で問題が発生しています。id属性を持つコンポーネントでビデオ要素をレンダリングするため、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

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

大量のデータを受け取るコンポーネントを印刷してキャンバスにペイントする必要があるとき、私は奇妙な状況にありました。私は言及されたすべてのアプローチを試してみましたが、それらのうちどれも確実に機能しませんでした.setTimeout内のrequestAnimationFrameを使用すると、20%の時間で空のキャンバスが表示されるため、次のようにしました:

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

基本的にはrequestAnimationFrameのチェーンを作成しましたが、これは良いアイデアかどうかはわかりませんが、これは今のところ100%のケースで機能します(n変数の値として30を使用しています)。


0

実際には、リクエストのアニメーションフレームやタイムアウトを使用するよりもはるかにシンプルでクリーンなバージョンがあります。Iamは誰もそれを持ち出したとは思わなかった: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>
  )
}

}


-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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.