componentDidMount wird BEFORE ref callback aufgerufen

86

Problem

Ich setze eine Reaktion refmit einer Inline-Funktionsdefinition

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

dann wird im componentDidMountDOM keine Referenz gesetzt

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

Meines refWissens nach sollte der Rückruf während des Mount ausgeführt werden. Das Hinzufügen von console.logAnweisungen componentDidMountwird jedoch vor der Ref-Rückruffunktion aufgerufen .

Andere Codebeispiele, die ich mir zum Beispiel in dieser Diskussion über Github angesehen habe, weisen auf dieselbe Annahme hin, componentDidMountdie nach allen in refdefinierten Rückrufen aufgerufen werden sollte. Dies wird rendersogar in der Konversation angegeben

Also wird componentDidMount ausgelöst, nachdem alle Ref-Rückrufe ausgeführt wurden?

Ja.

Ich benutze reagieren 15.4.1

Etwas anderes habe ich versucht

Um zu überprüfen, ob die refFunktion aufgerufen wurde, habe ich versucht, sie für die Klasse als solche zu definieren

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

dann in render

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

Die Konsolenprotokollierung in diesem Fall zeigt, dass der Rückruf tatsächlich nachher aufgerufen wird componentDidMount

Quickshiftin
quelle
6
Ich kann mich irren, aber wenn Sie die Pfeilfunktion für die Rendermethode verwenden, wird der Wert von thisaus dem lexikalischen Bereich außerhalb Ihrer Klasse erfasst . Versuchen Sie, die Pfeilfunktionssyntax für Ihre Klassenmethoden zu entfernen, und prüfen Sie, ob dies hilfreich ist.
Yoshi
3
@ GProst Das ist die Natur meiner Frage. Ich habe console.log in beide Funktionen eingefügt und componentDidMount wird zuerst ausgeführt, der ref-Rückruf an zweiter Stelle.
Quickshiftin
3
Gerade hatte ein ähnliches Issue-- für uns, im Grunde haben wir es verpasst auf die anfängliche renderund damit sein leveraging erforderlich componentDidUpdate, da componentDidMountnicht Teil der Aktualisierung ist Lebenszyklus . Wahrscheinlich nicht Ihr Problem, aber ich dachte, es könnte sich lohnen, es als mögliche Lösung anzusprechen.
Alexander Nied
4
Gleiches gilt für Reaktion 16. In der Dokumentation wird klar angegeben, ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.aber dies scheint nicht zu stimmen :(
Ryan H.
1
1. Die Deklaration des ref = {ref => { this.drawerRef = ref }}Referenzpfeils lautet: 2. Sogar Refs werden vor componentDidMount aufgerufen. Auf ref kann nur nach dem ersten Rendern zugegriffen werden, wenn das div in Ihrem Fall gerendert wird. Sie müssen also in der Lage sein, auf die Referenz in der nächsten Ebene zuzugreifen, dh in componentWillReceiveProps mit this.drawerRef3. Wenn Sie versuchen, vor dem ersten Mounten darauf zuzugreifen, erhalten Sie nur einen der beiden undefinierten Werte von ref.
bh4r4th

Antworten:

153

Kurze Antwort:

React garantiert, dass Refs vor componentDidMountoder componentDidUpdateHooks gesetzt werden. Aber nur für Kinder, die tatsächlich gerendert wurden .

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

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

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

Beachten Sie, dass dies nicht bedeutet, dass "React immer alle Refs setzt, bevor diese Hooks ausgeführt werden".
Schauen wir uns einige Beispiele an, bei denen die Refs nicht gesetzt werden.


Refs werden nicht für Elemente gesetzt, die nicht gerendert wurden

React ruft nur ref-Rückrufe für Elemente auf, die Sie tatsächlich vom Rendern zurückgegeben haben .

Dies bedeutet, wenn Ihr Code aussieht

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

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

und anfangs this.state.isLoadingist true, sollten Sie nicht erwarten , this._setRefbevor aufgerufen werden componentDidMount.

Dies sollte sinnvoll sein: Wenn Ihr erstes Rendering zurückgegeben wird <h1>Loading</h1>, kann React nicht erkennen, dass es unter anderen Bedingungen etwas anderes zurückgibt, für das ein Ref angehängt werden muss. Es gibt auch nichts, auf das der Verweis gesetzt werden kann: Das <div>Element wurde nicht erstellt, da die render()Methode sagte, dass es nicht gerendert werden sollte.

In diesem Beispiel wird also nur componentDidMountausgelöst. Wennthis.state.loadingfalse Sie jedoch Änderungen an vornehmen , wird dies this._setRefzuerst angehängt und dann ausgelöstcomponentDidUpdate .


Achten Sie auf andere Komponenten

Beachten Sie, dass Kinder, die Refs an andere Komponenten weitergeben, möglicherweise etwas tun, das das Rendern verhindert (und das Problem verursacht).

Zum Beispiel:

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

würde nicht funktionieren, wenn MyPanelnicht props.childrenin seiner Ausgabe enthalten:

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

Auch hier ist es kein Fehler: Für React gibt es nichts, auf das der Verweis gesetzt werden könnte, da das DOM-Element nicht erstellt wurde .


Refs werden nicht vor Lebenszyklen festgelegt, wenn sie an einen verschachtelten übergeben werden ReactDOM.render()

Ähnlich wie im vorherigen Abschnitt ist es möglich, dass diese Komponente, wenn Sie ein untergeordnetes Element mit einem Verweis an eine andere Komponente übergeben, etwas unternimmt, das das rechtzeitige Anhängen des Verweises verhindert.

Zum Beispiel wird das Kind möglicherweise nicht zurückgegeben render(), sondern es wird stattdessen ReactDOM.render()ein Lebenszyklus-Hook aufgerufen. Ein Beispiel dafür finden Sie hier . In diesem Beispiel rendern wir:

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

Aber MyModalführt einen ReactDOM.render()Aufruf in seinem componentDidUpdate Lebenszyklus - Methode:

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

render() {
  return null;
}

Seit React 16 werden solche Renderaufrufe der obersten Ebene während eines Lebenszyklus verzögert, bis Lebenszyklen für den gesamten Baum ausgeführt wurden . Dies würde erklären, warum Sie die Refs nicht rechtzeitig sehen.

Die Lösung für dieses Problem besteht darin, Portale anstelle von verschachtelten ReactDOM.renderAufrufen zu verwenden:

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

Auf diese Weise ist unser <div>mit einem Verweis tatsächlich in der Renderausgabe enthalten.

Wenn Sie also auf dieses Problem stoßen, müssen Sie überprüfen, ob zwischen Ihrer Komponente und dem Verweis nichts vorhanden ist, was das Rendern von untergeordneten Elementen verzögern könnte.

Nicht setStatezum Speichern von Refs verwenden

Stellen Sie sicher, dass Sie setStateden Ref nicht im Ref-Rückruf speichern, da er asynchron ist und zuerst ausgeführt wird, bevor er "fertig" ist componentDidMount.


Immer noch ein Problem?

Wenn keiner der oben genannten Tipps hilft, melden Sie ein Problem in React und wir werden einen Blick darauf werfen.

Dan Abramov
quelle
2
Ich habe meine Antwort bearbeitet, um auch diese Situation zu erklären. Siehe ersten Abschnitt. Hoffe das hilft!
Dan Abramov
Hallo @DanAbramov, danke dafür! Leider konnte ich keinen reproduzierbaren Fall entwickeln, als ich ihn zum ersten Mal sah. Leider arbeite ich nicht mehr an diesem Projekt und konnte seitdem keine Repro mehr durchführen. Die Frage ist jedoch so populär geworden, dass ich zustimme, dass der Versuch, den reproduzierbaren Fall zu finden, der Schlüssel ist, da viele Leute das Problem zu erleben scheinen.
Quickshiftin
Ich denke, es ist in vielen Fällen wahrscheinlich, dass dies auf ein Missverständnis zurückzuführen ist. In React 15 kann dies auch aufgrund eines verschluckten Fehlers passieren (React 16 hat eine bessere Fehlerbehandlung und verhindert dies). Gerne überprüfe ich in diesem Fall weitere Fälle. Fügen Sie sie also in den Kommentaren hinzu.
Dan Abramov
Hilft! Mir ist nicht wirklich aufgefallen, dass es einen Preloader gibt.
Nazariy
1
Diese Antwort hat mir wirklich geholfen. Ich hatte Probleme mit einer leeren "Refs" -Referenz, und es stellte sich heraus, dass die "Elemente" überhaupt nicht gerendert wurden.
MarkSkayff
1

Eine andere Beobachtung des Problems.

Ich habe festgestellt, dass das Problem nur im Entwicklungsmodus aufgetreten ist. Nach weiteren Untersuchungen stellte ich fest, dass das Deaktivieren react-hot-loaderin meiner Webpack-Konfiguration dieses Problem verhindert.

ich benutze

  • "React-Hot-Loader": "3.1.3"
  • "Webpack": "4.10.2",

Und es ist eine Elektronen-App.

Meine teilweise Webpack-Entwicklungskonfiguration

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'
  ],
  ...
})

Es wurde verdächtig, als ich sah, dass die Verwendung der Inline-Funktion in render () funktionierte, die Verwendung einer gebundenen Methode jedoch abstürzte.

Funktioniert auf jeden Fall

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

Absturz mit React-Hot-Loader (ref ist in componentDidMount undefiniert)

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

  inputRef (input) {
    this.inputField = input
  }

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

Um ehrlich zu sein, war Hot Reload oft problematisch, um "richtig" zu werden. Da Entwickler-Tools schnell aktualisiert werden, hat jedes Projekt eine andere Konfiguration. Vielleicht könnte meine spezielle Konfiguration behoben werden. Ich werde Sie hier informieren, wenn dies der Fall ist.

Kev
quelle
Dies könnte erklären, warum ich in CodePen Probleme damit habe, aber die Verwendung einer Inline-Funktion hat in meinem Fall nicht geholfen.
Robartsd
0

Das Problem kann auch auftreten, wenn Sie versuchen, eine Referenz einer nicht gemounteten Komponente zu verwenden, z. B. eine Referenz in setinterval, und das festgelegte Intervall während der Bereitstellung der Komponente nicht löschen.

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

immer klares Intervall wie zum Beispiel,

componentWillUnmount(){
    clearInterval(interval_holder)
}
zufälliger Codierer
quelle