web-dev-qa-db-fra.com

Redimensionner la vue du navigateur avec le navigateur React

Comment puis-je obtenir que React re-rende la vue lorsque la fenêtre du navigateur est redimensionnée?

Contexte

Il y a des blocs que je souhaite mettre en page individuellement sur la page, mais je souhaite également qu'ils soient mis à jour lorsque la fenêtre du navigateur change. Le résultat final sera quelque chose comme: mise en page de Ben Holland Pinterest, mais écrit en utilisant React et pas seulement jQuery. Je suis encore loin.

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

Ensuite, j'ai le composant Block (équivalent à un Pin dans l'exemple de 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 le redimensionnement de la fenêtre de jQuery? Si oui, où?

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

Y a-t-il une manière plus "Réactive" de faire cela?

258
digibake

Vous pouvez écouter composantDidMount, quelque chose comme ce composant qui affiche uniquement 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);
    }
});
399
Sophie Alpert

@SophieAlpert a raison, +1, je veux juste fournir une version modifiée de sa solution, sans jQuery , basée 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);
    }
});
125
André Pena

Une solution très simple:

resize = () => this.forceUpdate()

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

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

C'est un exemple simple et bref d'utilisation de 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>
    );
  }
}

crochets

import React, { useEffect, useState } from "react";

let App = () => {
  const [windowWidth, setWindowWidth] = useState(0);
  const [windowHeight, setWindowHeight] = useState(0);
  let resizeWindow = () => {
    setWindowWidth(window.innerWidth);
    setWindowHeight(window.innerHeight);
  };

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

  return (
    <div>
      <span>
        {windowWidth} x {windowHeight}
      </span>
    </div>
  );
};
31
voice

A partir de React 16.8, vous pouvez utiliser crochets !

/* 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 <>Width: {width}</>
}
16
Alex

Éditer 2018 : maintenant React a un support de première classe pour context


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

Si vous ne vous souciez pas des librairies à effets secondaires, vous pouvez simplement utiliser quelque chose comme Packery

Si vous utilisez Flux, vous pouvez créer un magasin contenant les propriétés de la fenêtre afin de conserver une fonction de rendu pure sans avoir à interroger l'objet window à chaque fois.

Dans les autres cas où vous souhaitez créer un site Web réactif mais que vous préférez React styles inline aux requêtes multimédia, ou souhaitez que le comportement HTML/JS change en fonction de la largeur de la fenêtre, continuez à lire:

Qu'est-ce que le contexte React et pourquoi j'en parle

contexte de réaction an n'est pas dans l'API publique et permet de transmettre des propriétés à toute une hiérarchie de composants.

Le contexte de réaction est particulièrement utile pour transmettre à l'ensemble de votre application des choses qui ne changent jamais (il est utilisé par de nombreux frameworks Flux via un mixin). Vous pouvez l'utiliser pour stocker des invariants de l'application (tels que l'ID utilisateur connecté, afin qu'ils soient disponibles partout).

Mais il peut aussi être utilisé pour stocker des choses qui peuvent changer. Le problème est que lorsque le contexte change, tous les composants qui l'utilisent doivent être restitués et ce n'est pas facile, la meilleure solution consiste souvent à démonter/remonter toute l'application avec le nouveau contexte. Rappelez-vous forceUpdate n'est pas récursif .

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

Ce qu'il faut mettre en contexte

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

Voici des choses qui ne changent pas souvent:

La langue de l'utilisateur actuel :

Cela ne change pas très souvent, et quand ça change, comme toute l'application est traduite, il faut tout refaire: une très belle utilisation du changement de langage à chaud

Les propriétés de la fenêtre

La largeur et la hauteur ne changent pas souvent, mais lorsque nous modifions notre présentation et notre comportement, il se peut que nous devions nous adapter. Pour la mise en page, il est parfois facile de personnaliser avec les médias CSS, mais parfois ce n’est pas le cas et nécessite une structure HTML différente. Pour le comportement, vous devez gérer cela avec Javascript.

Vous ne voulez pas tout refaire à chaque événement de redimensionnement, vous devez donc annuler les événements de redimensionnement.

D'après ce que je comprends de votre problème, vous voulez savoir combien d'éléments doivent être affichés en fonction de la largeur de l'écran. Donc, vous devez d’abord définir points d’intervention réactifs , puis énumérer le nombre de types de disposition que vous pouvez avoir.

Par exemple:

  • Layout "1col", pour largeur <= 600
  • Layout "2col", pour 600 <largeur <1000
  • Layout "3col", pour 1000 <= largeur

Lors des événements de redimensionnement (rejeté), vous pouvez facilement obtenir le type de présentation actuel en interrogeant l'objet window.

Ensuite, vous pouvez comparer le type de présentation avec l'ancien type et, s'il a changé, restituer l'application avec un nouveau contexte: vous éviterez ainsi de rendre à nouveau l'application lorsque l'utilisateur a déclenché des événements de redimensionnement mais le type de présentation n'a pas changé, vous ne devez donc effectuer un nouveau rendu que lorsque vous en avez besoin.

Une fois que vous avez cela, vous pouvez simplement utiliser le type de disposition dans votre application (accessible via le contexte) afin de pouvoir personnaliser le code HTML, le comportement, les classes CSS ... Vous connaissez votre type de disposition dans le React La fonction de rendu signifie donc que vous pouvez écrire en toute sécurité des sites Web réactifs en utilisant des styles en ligne, sans avoir besoin de médias.

Si vous utilisez Flux, vous pouvez utiliser un magasin au lieu du contexte React, mais si votre application comporte de nombreux composants réactifs, il est peut-être plus simple d'utiliser le contexte?

14
Sebastien Lorber

J'utilise la solution de @senornestor, mais pour être tout à fait correct, vous devez également supprimer l'écouteur d'événements:

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

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

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

Sinon, vous recevrez l'avertissement:

Avertissement: forceUpdate (...): ne peut mettre à jour qu'un composant monté ou monté. Cela signifie généralement que vous avez appelé forceUpdate () sur un composant non monté. C'est un no-op. Veuillez vérifier le code du composant XXX.

10
gkri

Ce code utilise le nouveau API de contexte React :

  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 <Provider value={this.state}>{this.props.children}</Provider>;
    }
  }

Ensuite, vous pouvez l’utiliser où vous voulez dans votre code comme ceci:

<WindowConsumer>
  {({ width, height }) =>  //do what you want}
</WindowConsumer>
7
Albert Olivé

Je sauterais toutes les réponses ci-dessus et commencerais à utiliser le composant react-dimensions Higher Order.

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

Ajoutez simplement un simple import et un appel de fonction, et vous pourrez 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
7
MindJuice

Vous n'avez pas nécessairement besoin de forcer un re-rendu.

Cela pourrait ne pas aider OP, mais dans mon cas, je n'avais besoin que de mettre à jour les attributs width et height sur mon canevas (ce que vous ne pouvez pas faire avec CSS).

Cela 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;
`
6
mpen

Pas sûr que ce soit la meilleure approche, mais ce qui a bien fonctionné pour moi a tout d'abord été de créer 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;

Ensuite, 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 votre information, ces fichiers ont été partiellement coupés.

5
David Sinclair

Je sais que cette question a été résolue, mais je pensais que je partagerais ma solution en tête de liste, bien que géniale, mais qui est peut-être un peu dépassée.

    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éborde (je n’utilise que toutes les 200 ms) les événements updateWindowDimensions sur l’événement resize pour augmenter légèrement les performances, MAIS je n’ai pas le faire rebondir lorsqu’on l’appelle sur ComponentDidMount.

Je trouvais que le rebond rendait le temps de montage assez lent parfois si vous vous retrouvez souvent dans une situation de montée.

Juste une optimisation mineure, mais espérons que cela aidera quelqu'un!

4
Matt Wills
componentDidMount() {

    // Handle resize
    window.addEventListener('resize', this.handleResize);
}




handleResize = () => {
    this.renderer.setSize(this.mount.clientWidth, this.mount.clientHeight);
    this.camera.aspect = this.mount.clientWidth / this.mount.clientHeight;
    this.camera.updateProjectionMatrix();
};

Seulement besoin de définir la fonction d'événement de redimensionnement.

Puis mettez à jour la taille des rendus (canevas), attribuez un nouveau rapport de format à la caméra.

Démonter et remonter est une solution folle à mon avis ....

ci-dessous est la monture si nécessaire.

            <div
                className={this.state.canvasActive ? 'canvasContainer isActive' : 'canvasContainer'}
                ref={mount => {
                    this.mount = mount;
                }}
            />
2
Nicolay Hekkens

https://github.com/renatorib/react-sizes est un HOC permettant de le faire tout en maintenant de bonnes performances.

import React from 'react'
import withSizes from 'react-sizes'

@withSizes(({ width }) => ({ isMobile: width < 480 }))
class MyComponent extends Component {
  render() {
    return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
  }
}

export default MyComponent
1
Russell Cohen

Juste pour améliorer la solution de @ senornestor d'utiliser forceUpdate et la solution de @ gkri pour supprimer l'écouteur d'événement resize lors du démontage du composant:

  1. n'oubliez pas de limiter (ou de réduire) l'appel à redimensionner
  2. assurez-vous de bind(this) dans le constructeur
import React from 'react'
import { throttle } from 'lodash'

class Foo extends React.Component {
  constructor(props) {
    super(props)
    this.resize = throttle(this.resize.bind(this), 100)
  }

  resize = () => this.forceUpdate()

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

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

  render() {
    return (
      <div>{window.innerWidth} x {window.innerHeight}</div>
    )
  }
}

Une autre méthode consiste simplement à utiliser un état "factice" au lieu de forceUpdate:

import React from 'react'
import { throttle } from 'lodash'

class Foo extends React.Component {
  constructor(props) {
    super(props)
    this.state = { foo: 1 }
    this.resize = throttle(this.resize.bind(this), 100)
  }

  resize = () => this.setState({ foo: 1 })

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

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

  render() {
    return (
      <div>{window.innerWidth} x {window.innerHeight}</div>
    )
  }
}
1
kimbaudi

Il a fallu le lier à 'this' dans le constructeur pour le faire fonctionner avec la syntaxe Class

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)
  }
}
1
Jim Perris

Je voulais partager cette jolie chose que je viens de découvrir en utilisant window.matchMedia

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

  useEffect(() => {
    // initial check to toggle something on or off
    toggle();

    // returns true when window is <= 768px
    mq.addListener(toggle);

    // unmount cleanup handler
    return () => mq.removeListener(toggle);
  }, []);

  // toggle something based on matchMedia event
  const toggle = () => {
    if (mq.matches) {
      // do something here
    } else {
      // do something here
    }
  };

.matches retournera true ou false si la fenêtre est supérieure ou inférieure à la valeur de largeur maximale spécifiée. Cela signifie qu'il n'est pas nécessaire d'étrangler l'écouteur, car matchMedia ne se déclenche qu'une fois lorsque la valeur booléenne est modifiée.

Mon code peut facilement être ajusté pour inclure useState afin de sauvegarder les déclarations booléennes matchMedia et de l'utiliser pour restituer sous condition un composant, une action déclenchée, etc.

1
Riley Brown

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
dumorango

Pour cette raison, il est préférable que vous utilisiez ces données à partir de données de fichier CSS ou JSON, puis que vous définissiez le nouvel état avec this.state ({width: "une valeur", height: "une valeur"}); ou l'écriture de code qui utilise des données d'écran de largeur dans le travail autonome si vous souhaitez afficher des images responsive

0
Miodrag Trajanovic