Redención de vista en el tamaño del navegador con React

¿Cómo puedo obtener React para volver a renderizar la vista cuando se cambia el tamaño de la ventana del navegador?

Fondo

Tengo algunos bloques que quiero distribuir de forma individual en la página, sin embargo, también quiero que se actualicen cuando cambie la ventana del navegador. El resultado final será algo así como el diseño de Pinterest de Ben Holland , pero escrito usando React no solo jQuery. Todavía estoy un poco lejos.

Código

Aquí está mi aplicación:

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 ( 
); } }); React.renderComponent( , document.getElementById('view') )

Luego tengo el componente Block (equivalente a un Pin en el ejemplo anterior de Pinterest):

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

{this.props.title}

{this.props.children}

); } });

y la lista / colección 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.description}; }); return ( 
{blockNodes}
); } });

Pregunta

¿Debo agregar el tamaño de ventana de jQuery? ¿Si es así, donde?

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

¿Hay una forma más “reactjsr” de hacer esto?

Puede escuchar en componentDidMount, algo así como este componente que solo muestra las dimensiones de la ventana (como 1024 x 768 ):

 var WindowDimensions = React.createClass({ render: function() { return {this.state.width} x {this.state.height}; }, 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 tiene razón, +1, solo quiero proporcionar una versión modificada de su solución, sin jQuery , en función de esta respuesta .

 var WindowDimensions = React.createClass({ render: function() { return {this.state.width} x {this.state.height}; }, 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); } }); 

Una solución muy simple:

 resize = () => this.forceUpdate() componentDidMount() { window.addEventListener('resize', this.resize) } componentWillUnmount() { window.removeEventListener('resize', this.resize) } 

Es un ejemplo simple y breve de usar es6 sin 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 (  {this.state.windowWidth} x {this.state.windowHeight}  ); } } 

Trataré de dar una respuesta genérica, que se dirija a este problema específico pero también a un problema más general.

Si no te importan las librerías de efectos secundarios, simplemente puedes usar algo como Packery

Si usa Flux, puede crear una tienda que contenga las propiedades de la ventana para que mantenga una función de renderización pura sin tener que consultar el objeto de ventana cada vez.

En otros casos en los que desee crear un sitio web receptivo pero prefiera Reaccionar estilos en línea a consultas de medios, o desee que el comportamiento HTML / JS cambie de acuerdo con el ancho de la ventana, siga leyendo:

¿Qué es el contexto de React y por qué hablo de eso?

El contexto de reacción y no está en la API pública y permite pasar propiedades a toda una jerarquía de componentes.

El contexto Reaccionar es particularmente útil para pasarle a tu aplicación cosas que nunca cambian (es usado por muchos frameworks Flux a través de un mixin). Puede usarlo para almacenar invariantes de negocios de aplicaciones (como el ID de usuario conectado, para que esté disponible en todas partes).

Pero también se puede usar para almacenar cosas que pueden cambiar. El problema es que cuando el contexto cambia, todos los componentes que lo usan deben volver a procesarse y no es fácil hacerlo, la mejor solución es a menudo desmontar / volver a montar toda la aplicación con el nuevo contexto. Recuerde que ForceUpdate no es recursivo .

Entonces, como usted entiende, el contexto es práctico, pero hay un impacto en el desempeño cuando cambia, por lo tanto, no debería cambiar con demasiada frecuencia.

Qué poner en contexto

  • Invariantes: como el userId conectado, sessionToken, lo que sea …
  • Cosas que no cambian a menudo

Aquí hay cosas que no cambian a menudo:

El idioma actual del usuario :

No cambia muy a menudo, y cuando lo hace, como toda la aplicación se traduce, tenemos que volver a renderizar todo: un muy útil caso de cambio de lengua caliente

Las propiedades de la ventana

El ancho y la altura no cambian a menudo, pero cuando hacemos nuestro diseño y el comportamiento puede tener que adaptarse. Para el diseño, a veces es fácil de personalizar con mediaqueries de CSS, pero a veces no es así y requiere una estructura HTML diferente. Para el comportamiento, debes manejar esto con Javascript.

No desea volver a procesar todo en cada evento de cambio de tamaño, por lo que debe atenuar los eventos de cambio de tamaño.

Lo que entiendo de su problema es que desea saber cuántos elementos mostrar de acuerdo con el ancho de la pantalla. Por lo tanto, primero debe definir puntos de interrupción receptivos y enumerar la cantidad de tipos de disposición diferentes que puede tener.

Por ejemplo:

  • Disposición “1col”, para ancho <= 600
  • Diseño “2col”, para 600
  • Diseño “3col”, para 1000 <= ancho

Al cambiar el tamaño de los eventos (antirrebote), puede obtener fácilmente el tipo de diseño actual consultando el objeto de la ventana.

Luego puede comparar el tipo de diseño con el anterior tipo de diseño, y si ha cambiado, vuelva a procesar la aplicación con un nuevo contexto: esto permite evitar volver a renderizar la aplicación cuando el usuario tiene eventos de ajuste de tamaño del disparador, pero en realidad el el tipo de diseño no ha cambiado, por lo que solo se vuelve a procesar cuando sea necesario.

Una vez que tenga eso, puede simplemente usar el tipo de diseño dentro de su aplicación (accesible a través del contexto) para que pueda personalizar el HTML, el comportamiento, las clases de CSS … Conoce su tipo de diseño dentro de la función Redactar, así que esto significa que puede escribir de forma segura sitios web receptivos mediante el uso de estilos en línea, y no necesita mediaqueries en absoluto.

Si usa Flux, puede usar una tienda en lugar de contexto Reaccionar, pero si su aplicación tiene muchos componentes receptivos, ¿es más fácil usar contexto?

Utilizo la solución de @senornestor, pero para ser completamente correcto, también debe eliminar el detector de eventos:

 componentDidMount() { window.addEventListener('resize', this.handleResize); } componentWillUnmount(){ window.removeEventListener('resize', this.handleResize); } handleResize = () => { this.forceUpdate(); }; 

De lo contrario, recibirá la advertencia:

Advertencia: forceUpdate (…): solo se puede actualizar un componente montado o montado. Esto generalmente significa que llamas a forceUpdate () en un componente desmontado. Esto es un no-op. Por favor, verifique el código del componente XXX.

Me saltaría todas las respuestas anteriores y comenzaré a usar el Componente de orden superior de las react-dimensions .

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

Simplemente agregue una import simple y una llamada a función, y puede acceder a this.props.containerWidth y this.props.containerHeight en su componente.

 // Example using ES6 syntax import React from 'react' import Dimensions from 'react-dimensions' class MyComponent extends React.Component { render() ( 
) } export default Dimensions()(MyComponent) // Enhanced component

No estoy seguro de si este es el mejor enfoque, pero lo que funcionó para mí fue la creación de una tienda, la llamé 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; 

Luego usé esa tienda en mis componentes, algo como esto:

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

FYI, estos archivos fueron parcialmente recortados.

No es necesario forzar una nueva representación.

Puede que esto no ayude a OP, pero en mi caso solo necesitaba actualizar los atributos de width y height en mi canvas (que no se puede hacer con CSS).

Se parece a esto:

 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; ` 

Sé que esto ha sido respondido, pero pensé que compartiría mi solución como la respuesta principal, aunque excelente, ahora puede estar un poco desactualizada.

  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 única diferencia es que estoy depurando (solo ejecutando cada 200 ms) updateWindowDimensions en el evento de cambio de tamaño para boost el rendimiento un poco, PERO no lo refinancia cuando se lo llama en ComponentDidMount.

Estaba encontrando que el antirrebote lo hacía bastante lento para montar a veces si tienes una situación en la que se está montando con frecuencia.

Solo una pequeña optimización, ¡pero espero que ayude a alguien!

Gracias a todos por las respuestas. Aquí está mi Reaccionar + Recomponer . Es una función de orden alto que incluye las propiedades windowHeight y windowWidth para el componente.

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

Este código usa la nueva API de contexto de 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() { const { children } = this.props; return {children}; } } export default WindowProvider; export const WindowConsumer = Consumer; 

Entonces puedes usarlo donde quieras en tu código así:

  {{(width, height)} => //do what you want}  

Tuve que vincularlo a ‘esto’ en el constructor para que funcione con la syntax de Clase

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

Por esta razón, es mejor si usa estos datos de datos de archivos CSS o JSON, y luego con esta configuración de datos nuevo estado con this.state ({width: “some value”, height: “some value”}); o escribir código que utiliza los datos de la pantalla de ancho en auto trabajo si desea mostrar las imágenes