Vue de Rerender sur le navigateur redimensionner avec React

Comment puis-je obtenir React pour redimensionner la vue lorsque la fenêtre du navigateur est redimensionnée?

arrière-plan

j'ai quelques blocs que je veux mettre en page individuellement sur la page, mais je veux aussi qu'ils soient mis à jour lorsque la fenêtre du navigateur change. Le résultat final sera quelque chose comme Ben Holland Pinterest layout, mais écrit en utilisant React pas seulement jQuery. Je suis toujours un moyen de sortir.

Code

Voici mon application:

var MyApp = React.createClass({
  //does the http get from the server
  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 (
        <div>
      <Blocks data={this.state.data}/>
      </div>
    );
  }
});

React.renderComponent(
  <MyApp url="url_here"/>,
  document.getElementById('view')
)

alors j'ai le composant Block (équivalent à un Pin dans l'exemple Pinterest ci-dessus):

var Block = React.createClass({
  render: function() {
    return (
        <div class="dp-block" style={{left: this.props.top, top: this.props.left}}>
        <h2>{this.props.title}</h2>
        <p>{this.props.children}</p>
        </div>
    );
  }
});

et la liste / collection de Blocks :

var Blocks = React.createClass({

  render: function() {

    //I've temporarily got code that assigns a random position
    //See inside the function below...

    var blockNodes = this.props.data.map(function (block) {   
      //temporary random position
      var topOffset = Math.random() * $(window).width() + 'px'; 
      var leftOffset = Math.random() * $(window).height() + 'px'; 
      return <Block order={block.id} title={block.summary} left={leftOffset} top={topOffset}>{block.description}</Block>;
    });

    return (
        <div>{blockNodes}</div>
    );
  }
});

Question

dois-je ajouter redimensionner la fenêtre de jQuery? Si oui, où?

$( window ).resize(function() {
  // re-render the component
});

y a-t-il une façon plus "réactive" de faire cela?

186
demandé sur Xufox 2013-09-26 00:06:06

14 réponses

vous pouvez écouter dans componentDidMount, quelque chose comme ce composant qui affiche juste les dimensions de la fenêtre (comme <span>1024 x 768</span> ):

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {
        this.setState({width: $(window).width(), height: $(window).height()});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});
306
répondu Sophie Alpert 2016-12-30 03:47:32

@SophieAlpert a raison, +1, je veux juste fournir une version modifiée de sa solution, sans jQuery , basé sur cette réponse .

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {

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

        this.setState({width: width, height: height});
        // if you are using ES2015 I'm pretty sure you can do this: this.setState({width, height});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});
95
répondu André Pena 2018-04-04 09:15:27

une solution très simple:

resize = () => this.forceUpdate()

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

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
32
répondu senornestor 2017-05-14 18:24:37

c'est un exemple simple et court d'utilisation d'es6 sans jQuery.

import React, { Component } from 'react';

export default class CreateContact extends Component {
  state = {
    windowHeight: undefined,
    windowWidth: undefined
  }

  handleResize = () => this.setState({
    windowHeight: window.innerHeight,
    windowWidth: window.innerWidth
  });

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

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

  render() {
    return (
      <span>
        {this.state.windowWidth} x {this.state.windowHeight}
      </span>
    );
  }
}
21
répondu voice 2018-04-04 19:54:28

Edit 2018 : React a maintenant un support de première classe pour contexte


je vais essayer de donner une réponse générique, qui cible ce problème spécifique, mais un problème plus général aussi.

si vous ne vous souciez pas des effets secondaires libs, vous pouvez simplement utiliser quelque chose comme Packery

si vous utilisez Flux, vous pouvez créer un magasin qui contenir les propriétés de fenêtre de sorte que vous gardez une fonction de rendu pure sans avoir à interroger l'objet de fenêtre à chaque fois.

dans d'autres cas où vous voulez construire un site web responsive mais que vous préférez réagir aux styles inline aux requêtes médias, ou que vous voulez que le comportement HTML/JS change en fonction de la largeur de la fenêtre, continuez à lire:

qu'est-Ce que Réagir contexte et pourquoi j'en parle

Réagir contexte n'est pas dans l'API publique et permet de passer des propriétés de toute une hiérarchie de composants.

le contexte React est particulièrement utile pour passer à l'ensemble de votre application des choses qui ne changent jamais (il est utilisé par de nombreux cadres de Flux à travers un mixin). Vous pouvez l'utiliser pour stocker des invariants d'application d'affaires (comme l'userId connecté, de sorte qu'il est disponible partout).

, Mais il peut également être utilisé pour stocker des choses qui peuvent changer. Le problème est que lorsque le contexte change, tous les composants utilisés, il devrait être re-rendus et il n'est pas facile à faire, la meilleure solution est souvent de démonter/remonter l'ensemble de l'application avec le nouveau contexte. Rappelez-vous forceUpdate n'est pas récursive .

comme vous le comprenez, le contexte est pratique, mais il y a un impact sur la performance quand il change, donc il ne devrait pas changer trop souvent.

que mettre en contexte

  • Invariants: comme l'userId connecté, sessionToken, peu importe...
  • choses qui ne changent pas souvent

Voici des choses qui ne changent pas souvent:

La langue de l'utilisateur courant :

il ne change pas très souvent, et quand il le fait, comme l'ensemble de l'application est traduit, nous devons tout re-rendre: une très belle usecase de chaud changement de langue

La fenêtre "propriétés de la 151920920"

largeur et hauteur pour ne pas changer souvent, mais quand nous faisons notre disposition et le comportement peut avoir à s'adapter. Pour la mise en page, il est parfois facile de la personnaliser avec des médiaqueries CSS, mais parfois elle ne l'est pas et nécessite une structure HTML différente. Pour le comportement, vous devez gérer cela avec Javascript.

vous ne voulez pas tout re-rendre sur chaque événement redimensionner, donc vous devez démystifier les événements redimensionner.

Ce que je comprends de votre problème est que vous voulez savoir combien d'articles à afficher en fonction de la largeur de l'écran. Vous devez donc d'abord définir responsive breakpoints , et énumérer le nombre de différents types de layout que vous pouvez avoir.

par exemple:

  • Layout " 1col", pour la largeur < = 600
  • Layout "2col", pour 600 < Largeur < 1000
  • Mise en page "3col", pour 1000 <= largeur

sur les événements redimensionnés (débonchés), vous pouvez facilement obtenir le type de layout actuel en interrogeant l'objet window.

alors vous pouvez comparer le type de layout avec l'ancien type de layout, et s'il a changé, re-rendre l'application avec un nouveau contexte: cela permet d'éviter de re-rendre l'application du tout quand l'Utilisateur a le déclenchement redimensionner les événements mais en fait le type de layout a pas changé, donc vous rendre à nouveau si nécessaire.

une fois que vous avez cela, vous pouvez simplement utiliser le type de layout à l'intérieur de votre application (accessible par le contexte) afin que vous puissiez personnaliser les classes HTML, behavior, CSS... Vous connaissez votre type de mise en page à l'intérieur de la fonction de rendu React, ce qui signifie que vous pouvez écrire en toute sécurité des sites Web responsive en utilisant des styles inline, et n'avez pas besoin de médiaqueries du tout.

si vous utilisez Flux, vous pouvez utiliser un magasin au lieu de React contexte, mais si votre application a beaucoup de composants sensibles c'est peut-être plus simple d'utiliser le contexte?

12
répondu Sebastien Lorber 2018-09-23 20:27:18

j'utilise la solution de @senornestor, mais pour être tout à fait correct vous devez supprimer l'écouteur d'événement aussi bien:

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

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

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

sinon vous aurez l'avertissement:

avertissement: forceUpdate(...): Peut seulement mettre à jour un montage composant. Cela signifie habituellement que vous avez appelé forceUpdate () sur un composant. C'est un no-op. Veuillez vérifier le code pour le XXX composant.

9
répondu gkri 2016-08-22 14:29:21

je sauterais toutes les réponses ci-dessus et je commencerais à utiliser la composante d'ordre supérieur react-dimensions .

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

il suffit d'ajouter un simple import et un appel de fonction, et vous pouvez accéder à this.props.containerWidth et this.props.containerHeight dans votre composant.

// Example using ES6 syntax
import React from 'react'
import Dimensions from 'react-dimensions'

class MyComponent extends React.Component {
  render() (
    <div
      containerWidth={this.props.containerWidth}
      containerHeight={this.props.containerHeight}
    >
    </div>
  )
}

export default Dimensions()(MyComponent) // Enhanced component
6
répondu MindJuice 2016-03-04 07:52:11

Je ne suis pas sûr que ce soit la meilleure approche, mais ce qui a fonctionné pour moi était d'abord la création d'un magasin, je l'ai appelé 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) {
    // Save within storage
    if(object) {
        object[key] = value;
    }

    // Persist to local storage
    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() {
        // Set defaults
        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;

alors j'ai utilisé ce magasin dans Mes composants, un peu comme ceci:

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) {
            // do something
        }

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

POUR INFO, ces fichiers ont été partiellement découpés.

5
répondu David Sinclair 2015-06-19 18:53:31

vous n'avez pas nécessairement besoin de forcer un rendu.

cela pourrait ne pas aider OP, mais dans mon cas, j'ai seulement eu besoin de mettre à jour les attributs width et height sur ma toile (ce que vous ne pouvez pas faire avec CSS).

il ressemble à ceci:

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 <canvas className={this.props.className} ref={this.setRef} />;
    }
}

export default styled(Canvas)`
   cursor: crosshair;
`
4
répondu mpen 2017-11-14 00:24:02

je sais que cela a été répondu, mais j'ai juste pensé que je partagerais ma solution comme la meilleure réponse, bien que grande, peut maintenant être un peu dépassé.

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

la seule différence, c'est que je débite (seulement tous les 200ms) les dimensions updatewindow sur l'événement resize pour augmenter un peu les performances, mais pas quand il est appelé ComponentDidMount.

j'ai trouvé que l'effraction le rendait assez lent à monter parfois si vous avez une situation où il monte souvent.

juste une petite optimisation mais j'espère que ça aidera quelqu'un!

2
répondu Matt Wills 2018-01-06 01:20:23

merci à tous pour les réponses. Voici mon React + Recompose . C'est une fonction D'ordre Élevé qui inclut les propriétés windowHeight et windowWidth du composant.

const withDimensions = compose(
 withStateHandlers(
 ({
   windowHeight,
   windowWidth
 }) => ({
   windowHeight: window.innerHeight,
   windowWidth: window.innerWidth
 }), {
  handleResize: () => () => ({
    windowHeight: window.innerHeight,
    windowWidth: window.innerWidth
  })
 }),
 lifecycle({
   componentDidMount() {
   window.addEventListener('resize', this.props.handleResize);
 },
 componentWillUnmount() {
  window.removeEventListener('resize');
 }})
)
1
répondu dumorango 2018-03-16 14:54:52

ce code utilise le nouveau React context 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() {
      const { children } = this.props;

      return <Provider value={this.state}>{children}</Provider>;
    }
  }

  export default WindowProvider;
  export const WindowConsumer = Consumer;

alors vous pouvez l'utiliser où vous voulez dans votre code comme ceci:

<WindowConsumer>
  {{(width, height)} => //do what you want}
</WindowConsumer>
1
répondu Albert Olivé 2018-07-02 11:22:26

a dû le lier à 'ceci' dans le constructeur pour le faire fonctionner avec la syntaxe de classe

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.resize = this.resize.bind(this)      
  }
  componentDidMount() {
    window.addEventListener('resize', this.resize)
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.resize)
  }
}
0
répondu Jim Perris 2017-10-13 04:52:16

pour cette raison mieux est si vous utilisez ces données à partir des données de fichier CSS ou JSON, et puis avec ce paramètre de données nouvel état avec ceci.état ({width: "some value", height:" some value"}); ou code d'écriture qui utilisent des données de données d'écran de largeur dans l'auto-travail si vous souhaitez responsive show images

0
répondu Miodrag Trajanovic 2018-06-22 19:49:53