webentwicklung-frage-antwort-db.com.de

Code "nach dem Rendern" reagieren?

Ich habe eine App, bei der ich die Höhe eines Elements (sagen wir "App-Inhalt") dynamisch einstellen muss. Es nimmt die Höhe des "Chrome" der App, subtrahiert es und stellt dann die Höhe des "App-Inhalts" so ein, dass er zu 100% innerhalb dieser Einschränkungen liegt. Dies ist mit Vanilla JS, jQuery oder Backbone-Ansichten sehr einfach, aber ich habe Schwierigkeiten, herauszufinden, was der richtige Prozess wäre, um dies in React durchzuführen.

Nachfolgend finden Sie eine Beispielkomponente. Ich möchte in der Lage sein, die Höhe von app-content auf 100% des Fensters abzüglich der Größe von ActionBar und BalanceBar einzustellen. Aber woher weiß ich, wann alles gerendert wird und wo ich die Berechnungssachen in diese React-Klasse stecke?

/** @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;
275
Oscar Godson

https://facebook.github.io/react/docs/react-component.html#componentdidmount

Diese Methode wird einmal aufgerufen, nachdem Ihre Komponente gerendert wurde. Ihr Code würde also so aussehen.

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

Ein Nachteil bei der Verwendung von componentDidUpdate oder componentDidMount ist, dass sie tatsächlich ausgeführt werden, bevor die dom-Elemente gezeichnet sind, aber nachdem sie von React an das DOM des Browsers übergeben wurden.

Sagen Sie zum Beispiel, wenn Sie node.scrollHeight auf das gerenderte node.scrollTop setzen müssen, dann reichen die DOM-Elemente von React möglicherweise nicht aus. Sie müssen warten, bis die Elemente fertig bemalt sind, um ihre Höhe zu erreichen.

Lösung:

Verwenden Sie requestAnimationFrame, um sicherzustellen, dass Ihr Code nach dem Malen Ihres neu gerenderten Objekts ausgeführt wird 

scrollElement: function() {
  //store a this ref, and
  var _this = this;
  //wait for a Paint to do scrolly stuff
  window.requestAnimationFrame(function() {
    var node = _this.getDOMNode();
    if (node !== undefined) {
      //and scroll them!
      node.scrollTop = node.scrollHeight;
    }
  });
},
componentDidMount: function() {
  this.scrollElement();
},
// and or
componentDidUpdate: function() {
  this.scrollElement();
},
// and or
render: function() {
  this.scrollElement()
  return [...]
197
Graham P Heath

Nach meiner Erfahrung war window.requestAnimationFrame nicht genug, um sicherzustellen, dass das DOM von componentDidMount vollständig gerendert/reflow-vollständig war. Ich habe Code ausgeführt, der unmittelbar nach einem componentDidMount-Aufruf auf das DOM zugreift und die alleinige Verwendung von window.requestAnimationFrame dazu führt, dass das Element im DOM vorhanden ist. Aktualisierungen der Dimensionen des Elements werden jedoch noch nicht berücksichtigt, da ein Reflow noch nicht erfolgt ist.

Der einzig wirklich zuverlässige Weg für diese Arbeit bestand darin, meine Methode in eine setTimeout und einen window.requestAnimationFrame zu packen, um sicherzustellen, dass der aktuelle Aufrufstapel von React gelöscht wird, bevor er sich für das Rendern des nächsten Frames registriert.

function onNextFrame(callback) {
    setTimeout(function () {
        window.requestAnimationFrame(callback)
    }, 0)
}

Wenn ich darüber spekulieren musste, warum dies so ist/notwendig ist, könnte ich sehen, dass React DOM-Aktualisierungen stapelt und die Änderungen nicht tatsächlich auf das DOM anwendet, bis der aktuelle Stapel abgeschlossen ist.

Wenn Sie im Code DOM-Messungen verwenden, die nach den React-Callbacks ausgelöst werden, möchten Sie wahrscheinlich diese Methode verwenden.

72
Elliot Chong

React verfügt über wenige Lebenszyklusmethoden, die in diesen Situationen hilfreich sind. Zu den Listen gehören unter anderem getInitialState, getDefaultProps, componentWillMount, componentDidMount usw.

In Ihrem Fall und in den Fällen, in denen mit den DOM-Elementen interagiert werden muss, müssen Sie warten, bis der Dom fertig ist. Verwenden Sie componentDidMount wie folgt:

/** @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;

Für weitere Informationen zum Lebenszyklus von reagieren können Sie den folgenden Link verwenden: https://facebook.github.io/react/docs/state-and-lifecycle.html

 getInitialState, getDefaultProps, componentWillMount, componentDidMount

7
Alireza

Ich habe das Gefühl, dass diese Lösung schmutzig ist, aber jetzt geht es los:

componentDidMount() {
    this.componentDidUpdate()
}

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

Jetzt sitze ich einfach hier und warte auf die abgelehnten Stimmen.

6
Jaakko Karhu

Nach dem Rendern können Sie die Höhe wie unten angegeben und die Höhe für die entsprechenden Reaktionskomponenten angeben.

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

oder Sie können die Höhe der einzelnen Reaktionskomponenten mit sass angeben. Geben Sie zuerst 2 Hauptkomponenten der Reaktionskomponente mit fester Breite und dann die Höhe der dritten Hauptkomponentenkomponente mit auto an. Basierend auf dem Inhalt des dritten Div wird die Höhe zugewiesen.

2

Sie können den Status ändern und dann Ihre Berechnungen im setState-Callback durchführen. Laut der React-Dokumentation ist dies "garantiert, nachdem das Update angewendet wurde".

Dies sollte in componentDidMount oder an einer anderen Stelle im Code (wie bei einem Ereignishandler für die Größenänderung) und nicht im Konstruktor erfolgen.

Dies ist eine gute Alternative zu window.requestAnimationFrame und weist nicht die Probleme auf, die einige Benutzer hier erwähnt haben (sie müssen mit setTimeout kombiniert oder mehrmals aufgerufen werden). Zum Beispiel:

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

Aus dem ReactDOM.render () Dokumentation:

Wenn der optionale Rückruf bereitgestellt wird, wird er nach dem .__ ausgeführt. Komponente wird gerendert oder aktualisiert.

2
Juampy NR

Ich habe ein Problem mit einem ähnlichen Verhalten. Ich rendere ein Videoelement in einer Komponente mit dem Attribut id. Wenn RenderDOM.render () beendet wird, lädt es ein Plugin, das die ID benötigt, um den Platzhalter zu finden.

Das setTimeout mit 0ms im ComponentDidMount () hat es behoben :)

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

Ein kleines Update mit ES6 Klassen anstelle von 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>
    );
  }
}

Überprüfen Sie auch die Lebenszyklusmethoden für React-Komponenten: https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle

Jede Komponente hat viele Methoden, die componentDidMount ähneln, z.

  • componentWillUnmount() - Komponente wird gerade aus dem Browser-DOM entfernt
1
pie6k

Ich bin auf das gleiche Problem gestoßen. 

In den meisten Szenarien, die den Hack-ish setTimeout(() => { }, 0) in componentDidMount() verwenden, hat es funktioniert.

Aber nicht in einem besonderen Fall. und ich wollte den ReachDOM findDOMNode nicht verwenden, da in der Dokumentation heißt:

Hinweis: findDOMNode ist eine Escape-Schraffur für den Zugriff auf das zugrunde liegende DOM Knoten. In den meisten Fällen wird von der Verwendung dieser Notluke abgeraten, da es durchdringt die Komponentenabstraktion.

(Quelle: https://facebook.github.io/react/docs/react-dom.html#finddomnode )

Also musste ich in dieser Komponente das componentDidUpdate() -Ereignis verwenden, sodass mein Code so aussah:

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

Und dann:

componentDidUpdate() {
    this.updateDimensions();
}

In meinem Fall musste ich schließlich den in componentDidMount erstellten Listener entfernen:

componentWillUnmount() {
    window.removeEventListener("resize", this.updateDimensions.bind(this));
}
0
ron.camaron

Ich hatte eine seltsame Situation, wenn ich eine Reakt-Komponente drucken muss, die große Datenmengen empfängt und auf Leinwand malen kann. Ich habe alle genannten Ansätze ausprobiert, von denen keine zuverlässig für mich funktionierte. Mit requestAnimationFrame in setTimeout bekomme ich in 20% der Zeit leere Leinwand. Daher habe ich Folgendes getan:

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

Grundsätzlich habe ich eine Kette von requestAnimationFrames gemacht, nicht sicher, ob dies eine gute Idee ist oder nicht, aber dies funktioniert für mich bisher in 100% der Fälle (ich verwende 30 als Wert für n Variable).

Um diese Frage mit den neuen Hook-Methoden ein wenig zu aktualisieren, können Sie einfach den Hook useEffect verwenden:

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

Auch wenn Sie vor dem Einhängen des Layouts ausführen möchten, verwenden Sie den Haken useLayoutEffect:

import React, { useLayoutEffect } from 'react'

export default function App(props) {

     useLayoutEffect(() => {
         // your post layout code (or 'effect') here.
         ...
     }, [])
}
0
P Fuster

Für mich führte keine Kombination aus window.requestAnimationFrame oder setTimeout zu konsistenten Ergebnissen. Manchmal hat es funktioniert, aber nicht immer - oder manchmal wäre es zu spät.

Ich habe es behoben, indem window.requestAnimationFrame so oft wie nötig wiederholt wurde.
(Normalerweise 0 oder 2-3 mal)

Der Schlüssel ist diff > 0: Hier können wir genau sicherstellen, wann die Seite aktualisiert wird.

// 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
jackcogdill