参照コールバックの前に呼び出されたcomponentDidMount


87

問題

refインライン関数定義を使用してreactを設定しています

render = () => {
    return (
        <div className="drawer" ref={drawer => this.drawerRef = drawer}>

次にcomponentDidMount、DOM参照が設定されていません

componentDidMount = () => {
    // this.drawerRef is not defined

私の理解では、refコールバックはマウント中に実行する必要がありますが、console.logステートメントを追加するcomponentDidMountと、refコールバック関数のに呼び出されます。

私は、例えば、見てきた他のコードサンプルこの議論githubの上には、同じ仮定を示すcomponentDidMountと呼ばれるべきの任意refに定義されたコールバックrenderそれはさえています、会話の中で述べました

それで、すべてのrefコールバックが実行された後、componentDidMountが起動されますか?

はい。

私はreact15.4.1を使用しています

私が試した他の何か

ref関数が呼び出されていることを確認するために、クラスで関数を定義してみました

setDrawerRef = (drawer) => {
  this.drawerRef = drawer;
}

その後、 render

<div className="drawer" ref={this.setDrawerRef}>

この場合のコンソールログは、コールバックが実際に呼び出さていることを示しています componentDidMount


6
私は間違っているかもしれませんが、レンダリング方法に矢印関数を使用している場合this、クラス外の字句スコープからの値をキャプチャします。クラスメソッドの矢印関数構文を削除して、それが役立つかどうかを確認してください。
ヨッシー

3
@GProstそれが私の質問の本質です。console.logを両方の関数に配置すると、componentDidMountが最初に実行され、次にrefコールバックが実行されます。
クイックシフトイン2017年

3
同様の問題がありました。基本的に、最初はそれを見逃していたため、更新ライフサイクルの一部ではないため、をrender活用する必要がありました。おそらくあなたの問題ではありませんが、潜在的な解決策として提起する価値があるかもしれないと考えました。componentDidUpdatecomponentDidMount
Alexander Nied 2017

4
React 16と同じです。ドキュメントには明確に記載されてref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.いますが、これは真実ではないようです:(
Ryan H.

1
1. refarrow宣言は次のとおりですref = {ref => { this.drawerRef = ref }}。2。componentDidMountの前にrefも呼び出されます。refにアクセスできるのは、最初のレンダリング後、ケースのdivがレンダリングされたときだけです。したがって、次のレベル、つまりthis.drawerRef3を使用してcomponentWillReceivePropsでrefにアクセスできる必要があります。初期マウントの前にアクセスしようとすると、refの未定義の値のみが取得されます。
bh4r4th

回答:


156

簡潔な答え:

レフリーが前に設定されていることを保証反応componentDidMountcomponentDidUpdateフックを。ただし、実際にレンダリングされた子供のみが対象です

componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

これは、「これらのフックが実行される前に、Reactが常にすべての参照を設定する」という意味ではないことに注意してください。
参照が設定されないいくつかの例を見てみましょう。


レンダリングされなかった要素に対して参照が設定されない

Reactは、実際にrenderから返された要素のrefコールバックのみを呼び出します

これは、コードが次のようになっている場合を意味します

render() {
  if (this.state.isLoading) {
    return <h1>Loading</h1>;
  }

  return <div ref={this._setRef} />;
}

最初this.state.isLoadingはですがtrue、前に呼び出されることを期待しないでください。this._setRefcomponentDidMount

これは理にかなっているはずです。最初のレンダリングが返された場合、<h1>Loading</h1>Reactが他の条件下で、参照を添付する必要のある何かを返すことを知る方法はありません。ありませんへの参照を設定するものは何も:<div>ので、要素が作成されていないrender()メソッドは、それがレンダリングされるべきではないと述べました。

したがって、この例では、のみcomponentDidMountが起動します。ただし、this.state.loading変更するとfalsethis._setRef最初に添付が表示され、次に起動しますcomponentDidUpdate


他のコンポーネントに注意してください

refを持つ子を他のコンポーネント渡すと、レンダリングを妨げる(そして問題を引き起こす)何かをしている可能性があることに注意してください

たとえば、これは次のとおりです。

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>

出力にMyPanel含まprops.childrenれていない場合は機能しません:

function MyPanel(props) {
  // ignore props.children
  return <h1>Oops, no refs for you today!</h1>;
}

繰り返しますが、これはバグではありません。DOM要素が作成されていないため、Reactが参照を設定することはありません


ネストされた参照に渡された場合、参照はライフサイクルの前に設定されません ReactDOM.render()

前のセクションと同様に、refを持つ子を別のコンポーネントに渡すと、このコンポーネントがrefを時間内にアタッチできないようにする可能性があります。

たとえば、から子を返さずrender()、代わりにReactDOM.render()ライフサイクルフックを呼び出している可能性があります。この例はここにあります。その例では、次のようにレンダリングします。

<MyModal>
  <div ref={this.setRef} />
</MyModal>

ただしライフサイクルメソッドで呼び出しをMyModal実行ます。ReactDOM.render() componentDidUpdate

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

React 16以降、ライフサイクル中のこのようなトップレベルのレンダリング呼び出しは、ツリー全体のライフサイクルが実行されるまで遅延されます。これは、参照が時間内に添付されていない理由を説明します。

この問題の解決策は、ネストされた呼び出しの代わりにポータルを使用することです ReactDOM.render

render() {
  return ReactDOM.createPortal(this.props.children, this.targetEl);
}

このようにし<div>て、refを使用したものが実際にレンダリング出力に含まれます。

したがって、この問題が発生した場合は、コンポーネントとrefの間に、子のレンダリングを遅らせる可能性のあるものがないことを確認する必要があります。

setState参照の保存には使用しないでください

setStaterefをrefコールバックに格納するために使用していないことを確認してください。これは非同期であり、「終了」する前にcomponentDidMount最初に実行されるためです。


まだ問題ですか?

上記のヒントのいずれも役に立たない場合は、Reactに問題を提出してください。調査します。


2
この状況を説明するために、回答を編集しました。最初のセクションを参照してください。お役に立てれば!
ダン・アブラモフ2018

こんにちは@DanAbramovこれに感謝します!残念ながら、最初に遭遇したとき、再現可能なケースを開発することができませんでした。悲しいことに、私はそのプロジェクトにもう取り組んでおらず、それ以来再現することができませんでした。多くの人々が問題を経験しているように見えるので、再現可能なケースを見つけようとすることが重要であると私は同意しますが、この質問は十分に人気があります。
クイックシフト

多くの場合、これは誤解によるものだと思います。React 15では、これは飲み込まれたエラーが原因でも発生する可能性があります(React 16はエラー処理が優れており、これを防ぎます)。このような場合は、さらに多くのケースを確認させていただきますので、コメントに自由に追加してください。
ダン・アブラモフ

助けて!プリローダーがあることに気づきませんでした。
ナザリ

1
この答えは本当に私を助けました。空の「refs」参照に苦労していましたが、「要素」がまったくレンダリングされていないことがわかりました。
MarkSkayff

1

問題の別の観察。

この問題は開発モードでのみ発生することに気づきました。さらに調査したところ、react-hot-loaderWebpack構成を無効にすると、この問題が回避されることがわかりました。

使ってます

  • 「react-hot-loader」:「3.1.3」
  • "webpack": "4.10.2"、

そして、それは電子アプリです。

私の部分的なWebpack開発構成

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')

module.exports = merge(baseConfig, {

  entry: [
    // REMOVED THIS -> 'react-hot-loader/patch',
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    '@babel/polyfill',
    './app/index'
  ],
  ...
})

render()でインライン関数を使用することが機能しているのを見て、疑わしくなりましたが、boundメソッドを使用するとクラッシュしていました。

とにかく動作します

class MyComponent {
  render () {
    return (
      <input ref={(el) => {this.inputField = el}}/>
    )
  }
}

react-hot-loaderでクラッシュします(refはcomponentDidMountで定義されていません)

class MyComponent {
  constructor (props) {
    super(props)
    this.inputRef = this.inputRef.bind(this)
  }

  inputRef (input) {
    this.inputField = input
  }

  render () {
    return (
      <input ref={this.inputRef}/>
    )
  }
}

正直なところ、ホットリロードは「正しく」行うのに問題があることがよくあります。開発ツールが高速に更新されるため、プロジェクトごとに異なる構成があります。たぶん私の特定の設定を修正することができます。その場合は、こちらでお知らせします。


これは、CodePenでこれに問題がある理由を説明しているかもしれませんが、インライン関数を使用しても私の場合は役に立ちませんでした。
robartsd

0

この問題は、setintervalでrefを使用するように、マウントされていないコンポーネントのrefを使用しようとし、コンポーネントのアンマウント中にsetintervalをクリアしない場合にも発生する可能性があります。

componentDidMount(){
    interval_holder = setInterval(() => {
    this.myref = "something";//accessing ref of a component
    }, 2000);
  }

たとえば、常に間隔をクリアします。

componentWillUnmount(){
    clearInterval(interval_holder)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.