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?

9voto

Albert Olivé Punkte 3721

Dieser Code verwendet die neue React-Kontext-API:

  import React, { PureComponent, createContext } from 'react';

  const { Provider, Consumer } = createContext({ width: 0, height: 0 });

  class WindowProvider extends PureComponent {
    state = this.getDimensions();

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

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

    getDimensions() {
      const w = window;
      const d = document;
      const documentElement = d.documentElement;
      const body = d.getElementsByTagName('body')[0];
      const width = w.innerWidth || documentElement.clientWidth || body.clientWidth;
      const height = w.innerHeight || documentElement.clientHeight || body.clientHeight;

      return { width, height };
    }

    updateDimensions = () => {
      this.setState(this.getDimensions());
    };

    render() {
      return {this.props.children};
    }
  }

Dann können Sie es überall in Ihrem Code wie folgt verwenden:

  {({ width, height }) =>  //tue, was du willst}

7voto

David Sinclair Punkte 3807

Nicht sicher, ob dies der beste Ansatz ist, aber was für mich funktioniert hat, war zunächst die Erstellung eines Stores. Ich nannte ihn WindowStore:

import {assign, events} from '../../libs';
import Dispatcher from '../dispatcher';
import Constants from '../constants';

let CHANGE_EVENT = 'change';
let defaults = () => {
    return {
        name: 'window',
        width: undefined,
        height: undefined,
        bps: {
            1: 400,
            2: 600,
            3: 800,
            4: 1000,
            5: 1200,
            6: 1400
        }
    };
};
let save = function(object, key, value) {
    // Speichern im Speicher
    if(object) {
        object[key] = value;
    }

    // Persistenz in lokalem Speicher
    sessionStorage[storage.name] = JSON.stringify(storage);
};
let storage;

let Store = assign({}, events.EventEmitter.prototype, {
    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
        window.addEventListener('resize', () => {
            this.updateDimensions();
            this.emitChange();
        });
    },
    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },
    get: function(keys) {
        let value = storage;

        for(let key in keys) {
            value = value[keys[key]];
        }

        return value;
    },
    initialize: function() {
        // Setze Standardwerte
        storage = defaults();
        save();
        this.updateDimensions();
    },
    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
        window.removeEventListener('resize', () => {
            this.updateDimensions();
            this.emitChange();
        });
    },
    updateDimensions: function() {
        storage.width =
            window.innerWidth ||
            document.documentElement.clientWidth ||
            document.body.clientWidth;
        storage.height =
            window.innerHeight ||
            document.documentElement.clientHeight ||
            document.body.clientHeight;
        save();
    }
});

export default Store;

Dann habe ich diesen Store in meinen Komponenten verwendet, so ungefähr:

import WindowStore from '../stores/window';

let getState = () => {
    return {
        windowWidth: WindowStore.get(['width']),
        windowBps: WindowStore.get(['bps'])
    };
};

export default React.createClass(assign({}, base, {
    getInitialState: function() {
        WindowStore.initialize();

        return getState();
    },
    componentDidMount: function() {
        WindowStore.addChangeListener(this._onChange);
    },
    componentWillUnmount: function() {
        WindowStore.removeChangeListener(this._onChange);
    },
    render: function() {
        if(this.state.windowWidth < this.state.windowBps[2] - 1) {
            // etwas tun
        }

        // zurückgeben
        return something;
    },
    _onChange: function() {
        this.setState(getState());
    }
}));

Übrigens, diese Dateien wurden teilweise gekürzt.

7voto

mpen Punkte 253762

Sie müssen nicht unbedingt ein erneutes Rendern erzwingen.

Dies hilft möglicherweise nicht dem OP, aber in meinem Fall musste ich nur die Attribute width und height auf meinem Canvas aktualisieren (was mit CSS nicht möglich ist).

Es sieht so aus:

import React from 'react';
import styled from 'styled-components';
import {throttle} from 'lodash';

class Canvas extends React.Component {

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

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

    resize = throttle(() => {
        this.canvas.width = this.canvas.parentNode.clientWidth;
        this.canvas.height = this.canvas.parentNode.clientHeight;
    },50)

    setRef = node => {
        this.canvas = node;
    }

    render() {
        return ;
    }
}

export default styled(Canvas)`
   cursor: crosshair;
`

6voto

Matt Wills Punkte 646

Ich weiß, dass dies bereits beantwortet wurde, aber ich dachte, ich würde meine Lösung teilen, da die beste Antwort möglicherweise etwas veraltet ist.

    constructor (props) {
      super(props)

      this.state = { width: '0', height: '0' }

      this.initUpdateWindowDimensions = this.updateWindowDimensions.bind(this)
      this.updateWindowDimensions = debounce(this.updateWindowDimensions.bind(this), 200)
    }

    componentDidMount () {
      this.initUpdateWindowDimensions()
      window.addEventListener('resize', this.updateWindowDimensions)
    }

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

    updateWindowDimensions () {
      this.setState({ width: window.innerWidth, height: window.innerHeight })
    }

Der einzige wirkliche Unterschied besteht darin, dass ich die updateWindowDimensions-Funktion beim resize-Event debounce (nur alle 200 ms ausführen), um die Leistung etwas zu verbessern, ABER nicht debouncen, wenn sie in ComponentDidMount aufgerufen wird.

Ich habe festgestellt, dass das Debounce manchmal zu einer recht trägen Montage führt, wenn Sie in einer Situation sind, in der es häufig montiert wird.

Nur eine kleine Optimierung, aber ich hoffe, es hilft jemandem!

5voto

Es gibt viele Köche in dieser Küche, aber ich werde trotzdem meinen Hut hineinwerfen. Keiner davon verwendet requestAnimationFrame, was meiner Meinung nach am performantesten ist.

Hier ist ein Beispiel für die Verwendung von React Hooks und requestAnimationFrame. Dies verwendet auch reinen JS ohne Bibliotheken wie lodash (die ich aufgrund der Bundle-Größe um jeden Preis vermeide).

import { useState, useEffect, useCallback } from 'react';

const getSize = () => {
  return { 
    width: window.innerWidth,
    height: window.innerHeight,
  };
};

export function useResize() {

  const [size, setSize] = useState(getSize());

  const handleResize = useCallback(() => {
    let ticking = false;
    if (!ticking) {
      window.requestAnimationFrame(() => {
        setSize(getSize());
        ticking = false;
      });
      ticking = true;
    } 
  }, []);

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

  return size;
}

Hier ist ein Gist, in dem es verwendet wird: Img.tsx mit useResize. Alternativ können Sie es in meinem Repository hier für mehr Kontext sehen.

Einige Ressourcen, warum Sie dies tun sollten, anstatt Ihre Funktion zu entpuffern:

Vielen Dank, dass Sie zu meinem Ted Talk gekommen sind.

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