496 Stimmen

Rerender-Ansicht beim Browser-Größenänderung mit React

Wie kann ich React dazu bringen, die Ansicht neu zu rendnern, wenn das Browserfenster neu skaliert wird?

Hintergrund

Ich habe einige Blöcke, die ich individuell auf der Seite anordnen möchte, aber ich möchte auch, dass sie sich ändern, wenn sich das Browserfenster ändert. Das Endergebnis wird letztendlich etwas ähnliches wie Ben Hollands Pinterest Layout sein, jedoch geschrieben in React und nicht nur jQuery. Ich bin noch weit davon entfernt.

Code

Hier ist meine App:

var MyApp = React.createClass({
  //führt das http get vom Server aus
  loadBlocksFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      mimeType: 'textPlain',
      success: function(data) {
        this.setState({data: data.events});
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentWillMount: function() {
    this.loadBlocksFromServer();

  },    
  render: function() {
    return (

    );
  }
});

React.renderComponent(
  ,
  document.getElementById('view')
)

Dann habe ich das Block Komponente (entspricht einem Pin im obigen Pinterest Beispiel):

var Block = React.createClass({
  render: function() {
    return (

        {this.props.title}
        {this.props.children}

    );
  }
});

und die Liste/Kollektion von Blocks:

var Blocks = React.createClass({

  render: function() {

    //Ich habe vorübergehend Code, der eine zufällige Position zuweist
    //Siehe innerhalb der folgenden Funktion...

    var blockNodes = this.props.data.map(function (block) {   
      //temporäre zufällige Position
      var topOffset = Math.random() * $(window).width() + 'px'; 
      var leftOffset = Math.random() * $(window).height() + 'px'; 
      return {block.description};   
    });

    return (
        {blockNodes}
    );
  }
});

Frage

Sollte ich das window resize von jQuery hinzufügen? Wenn ja, wo?

$( window ).resize(function() {
  // Komponente neu rendern
});

Gibt es eine "React"-artige Möglichkeit, dies zu tun?

30voto

noetix Punkte 4693

Ab React 16.8 können Sie Hooks verwenden!

/* globals window */
import React, { useState, useEffect } from 'react'
import _debounce from 'lodash.debounce'

const Example = () => {
  const [width, setWidth] = useState(window.innerWidth)

  useEffect(() => {
    const handleResize = _debounce(() => setWidth(window.innerWidth), 100)

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    }
  }, [])

  return <>Breite: {width}
}

18voto

Sebastien Lorber Punkte 83875

Bearbeiten 2018: Jetzt hat React Unterstützung für Context


Ich werde versuchen, eine generische Antwort zu geben, die dieses spezifische Problem und auch ein allgemeineres Problem anspricht.

Wenn Sie sich nicht um Nebeneffekte von Bibliotheken kümmern, können Sie einfach etwas wie Packery verwenden.

Wenn Sie Flux verwenden, könnten Sie einen Store erstellen, der die Fenstereigenschaften enthält, sodass Sie eine reine Renderfunktion beibehalten können, ohne jedes Mal das Fensterobjekt abfragen zu müssen.

In anderen Fällen, in denen Sie eine responsive Website erstellen möchten, jedoch React-Inline-Styles anstelle von Media Queries bevorzugen oder das HTML/JS-Verhalten entsprechend der Fensterbreite ändern möchten, lesen Sie weiter:

Was ist React-Context und warum ich darüber spreche

React-Context ist nicht Teil der öffentlichen API und ermöglicht es, Eigenschaften an eine ganze Hierarchie von Komponenten weiterzugeben.

React-Context ist besonders nützlich, um Dinge an Ihre gesamte App weiterzugeben, die sich nie ändern (es wird von vielen Flux-Frameworks über ein Mixin verwendet). Sie können es verwenden, um App-Geschäftsinvarianzen zu speichern (wie die verbundene Benutzer-ID, damit sie überall verfügbar ist).

Aber es kann auch verwendet werden, um Dinge zu speichern, die sich ändern können. Das Problem ist, dass, wenn sich der Kontext ändert, alle Komponenten, die ihn verwenden, neu gerendert werden sollten, und es ist nicht einfach, dies zu tun. Die beste Lösung besteht oft darin, die gesamte App mit dem neuen Kontext zu demontieren/wiederzuinstallieren. Denken Sie daran, dass forceUpdate nicht rekursiv ist.

Wie Sie verstehen, ist der Kontext praktisch, aber es gibt eine Leistungsauswirkung, wenn er sich ändert, daher sollte er lieber nicht zu oft geändert werden.

Was in den Kontext stellen

  • Invarianzen: wie die verbundene Benutzer-ID, die Sitzungstoken, was auch immer...
  • Dinge, die sich nicht oft ändern

Hier sind Dinge, die sich nicht oft ändern:

Die aktuelle Benutzersprache:

Es ändert sich nicht sehr oft, und wenn es das tut, da die gesamte App übersetzt wird, müssen wir alles neu rendern: ein sehr schönes Anwendungsfallszenario für einen heißen Sprachwechsel

Die Fenstereigenschaften

Breite und Höhe ändern sich nicht oft, aber wenn wir unser Layout und Verhalten anpassen müssen. Für das Layout ist es manchmal einfach, mit CSS-Mediaqueries anzupassen, aber manchmal ist es nicht und erfordert eine andere HTML-Struktur. Für das Verhalten müssen Sie dies mit Javascript behandeln.

Sie möchten nicht alles bei jedem Größenänderungsevent neu rendern, also müssen Sie die Größenänderungsevents drosseln.

Was ich von Ihrem Problem verstehe, ist, dass Sie wissen möchten, wie viele Elemente je nach Bildschirmbreite angezeigt werden sollen. Also müssen Sie zunächst reaktionsfähige Breakpoints definieren und die Anzahl der verschiedenen Layouttypen angeben, die Sie haben können.

Zum Beispiel:

  • Layou "1 Spalte", für Breite <= 600
  • Layout "2 Spalten", für 600 < Breite < 1000
  • Layout "3 Spalten", für 1000 <= Breite

Bei Größenänderungsevents (gedrosselt) können Sie den aktuellen Layouttyp problemlos abrufen, indem Sie das Fensterobjekt abfragen.

Dann können Sie den Layouttyp mit dem früheren Layouttyp vergleichen, und wenn er sich geändert hat, die App mit einem neuen Kontext neu rendern: Dadurch wird vermieden, dass die App überhaupt neu gerendert wird, wenn der Benutzer Größenänderungsevents ausgelöst hat, aber tatsächlich der Layouttyp nicht geändert wurde. So rendern Sie nur bei Bedarf neu.

Wenn Sie das haben, können Sie einfach den Layouttyp innerhalb Ihrer App verwenden (zugänglich durch den Kontext), sodass Sie das HTML, Verhalten, CSS-Klassen... anpassen können. Sie kennen Ihren Layouttyp innerhalb der React-Renderfunktion, was bedeutet, dass Sie problemlos responsive Websites durch Verwenden von Inline-Styles schreiben können und überhaupt keine Media-Queries benötigen.

Wenn Sie Flux verwenden, können Sie anstelle von React-Context einen Store verwenden, aber wenn Ihre App viele reaktionsfähige Komponenten hat, ist es vielleicht einfacher, Context zu verwenden?

12voto

gkri Punkte 1467

Ich benutze @senornestor's Lösung, aber um völlig korrekt zu sein, musst du auch den Event-Listener entfernen:

componentDidMount() {
    window.addEventListener('resize', this.handleResize);
}

componentWillUnmount(){
    window.removeEventListener('resize', this.handleResize);
}

handleResize = () => {
    this.forceUpdate();
};

Andernfalls erhalten Sie die Warnung:

Warnung: forceUpdate(...): Kann nur ein montiertes oder montierendes Komponente aktualisieren. Dies bedeutet normalerweise, dass Sie forceUpdate() auf einer demontierten Komponente aufgerufen haben. Dies ist ein Leerlauf. Bitte überprüfen Sie den Code für die XXX Komponente.

11voto

Riley Brown Punkte 111

Möchte diese ziemlich coole Sache teilen, die ich gerade mit window.matchMedia gefunden habe

const mq = window.matchMedia('(max-width: 768px)');

  useEffect(() => {
    // Initialer Check zum Ein- oder Ausschalten von etwas
    toggle();

    // Gibt true zurück, wenn das Fenster <= 768px ist
    mq.addListener(toggle);

    // Handler zum Aufräumen beim Entfernen
    return () => mq.removeListener(toggle);
  }, []);

  // Etwas basierend auf dem matchMedia-Ereignis umschalten
  const toggle = () => {
    if (mq.matches) {
      // Hier etwas tun
    } else {
      // Hier etwas tun
    }
  };

.matches gibt true oder false zurück, wenn das Fenster höher oder niedriger als der angegebene max-width-Wert ist, dies bedeutet, dass kein throttling des Listeners erforderlich ist, da das matchMedia nur einmal ausgelöst wird, wenn sich der Wert ändert.

Mein Code kann leicht angepasst werden, um useState einzuschließen, um den booleschen Wert, den matchMedia zurückgibt, zu speichern, und ihn verwenden, um eine Komponente bedingt zu rendern, eine Aktion auszulösen usw.

10voto

MindJuice Punkte 3841

Ich würde alle obigen Antworten überspringen und anfangen, das react-dimensions Higher Order Component zu verwenden.

https://github.com/digidem/react-dimensions

Fügen Sie einfach einen einfachen import hinzu und einen Funktionsaufruf, und Sie können auf this.props.containerWidth und this.props.containerHeight in Ihrem Komponente zugreifen.

// Beispiel unter Verwendung der ES6-Syntax
import React from 'react'
import Dimensions from 'react-dimensions'

class MyComponent extends React.Component {
  render() (

  )
}

export default Dimensions()(MyComponent) // Verbesserte Komponente

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X