Comment puis-je obtenir que React re-rende la vue lorsque la fenêtre du navigateur est redimensionnée?
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.
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>
);
}
});
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?
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);
}
});
@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);
}
});
Une solution très simple:
resize = () => this.forceUpdate()
componentDidMount() {
window.addEventListener('resize', this.resize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize)
}
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>
);
};
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}</>
}
É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
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:
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?
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.
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>
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
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;
`
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.
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!
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;
}}
/>
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
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:
bind(this)
dans le constructeurimport 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>
)
}
}
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)
}
}
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.
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');
}})
)
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