javascript - pagina - react official
Rerender vista en el navegador cambiar el tamaƱo con React (14)
¿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 diseñar individualmente en la página, sin embargo, también quiero que se actualicen cuando cambie la ventana del navegador. El resultado final será algo como el diseño de Pinterest de Ben Holland , pero escrito usando React no solo jQuery. Todavía estoy 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 (
<div>
<Blocks data={this.state.data}/>
</div>
);
}
});
React.renderComponent(
<MyApp url="url_here"/>,
document.getElementById(''view'')
)
Luego tengo el componente Block
(equivalente a un Pin
en el ejemplo de Pinterest anterior):
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>
);
}
});
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 order={block.id} title={block.summary} left={leftOffset} top={topOffset}>{block.description}</Block>;
});
return (
<div>{blockNodes}</div>
);
}
});
Pregunta
¿Debo agregar el tamaño de la ventana de jQuery? ¿Si es así, donde?
$( window ).resize(function() {
// re-render the component
});
¿Hay una manera más "Reaccionar" de hacer esto?
@SophieAlpert tiene razón, +1, solo quiero proporcionar una versión modificada de su solución, sin jQuery , basada en esta respuesta .
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);
}
});
Es un ejemplo simple y corto 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 (
<span>
{this.state.windowWidth} x {this.state.windowHeight}
</span>
);
}
}
Este código está utilizando la nueva context :
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;
Entonces puedes usarlo donde quieras en tu código de esta manera:
<WindowConsumer>
{{(width, height)} => //do what you want}
</WindowConsumer>
Gracias a todos por las respuestas. Aquí está mi React + Recompose . Es una función de orden superior 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'');
}})
)
Me saltearía todas las respuestas anteriores y comenzaría a usar el componente de orden superior de react-dimensions
.
https://github.com/digidem/react-dimensions
Simplemente agregue una import
simple y una llamada a la función, y podrá 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() (
<div
containerWidth={this.props.containerWidth}
containerHeight={this.props.containerHeight}
>
</div>
)
}
export default Dimensions()(MyComponent) // Enhanced component
No estoy seguro de si este es el mejor enfoque, pero lo que funcionó para mí fue crear una Tienda, lo 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());
}
}));
Para su información, estos archivos fueron recortados parcialmente.
No necesariamente es necesario forzar una nueva representación.
Esto podría no ayudar a OP, pero en mi caso solo necesitaba actualizar los atributos de width
y height
en mi lienzo (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 <canvas className={this.props.className} ref={this.setRef} />;
}
}
export default styled(Canvas)`
cursor: crosshair;
`
Por esta razón, es mejor si utiliza estos datos de los datos de archivos CSS o JSON, y luego con estos datos configurando un nuevo estado con this.state ({width: "some value", height: "some value"}); o escribir código que use datos de datos de pantalla de ancho en el trabajo por cuenta propia si desea mostrar imágenes de respuesta
Puede escuchar en componentDidMount, algo como este componente que solo muestra las dimensiones de la ventana (como <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);
}
});
Sé que esto ha sido respondido pero solo pensé que compartiría mi solución como la mejor respuesta, aunque excelente, puede que ahora esté 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 realmente es que estoy publicando (solo ejecutando cada 200 ms) las updateWindowDimensions en el evento de cambio de tamaño para aumentar un poco el rendimiento, PERO no lo hacemos cuando se llama en ComponentDidMount.
Estaba encontrando que el rebote hacía que se retrasara bastante el montaje a veces si tienes una situación en la que a menudo se está acumulando.
Sólo una pequeña optimización, pero espero que ayude a alguien!
Tenía que vincularlo a ''this'' en el constructor para que funcionara con la sintaxis de 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)
}
}
Una solución muy simple:
resize = () => this.forceUpdate()
componentDidMount() {
window.addEventListener(''resize'', this.resize)
}
componentWillUnmount() {
window.removeEventListener(''resize'', this.resize)
}
Utilizo la solución de @senornestor, pero para ser totalmente 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 de montaje. Esto generalmente significa que usted llamó a forceUpdate () en un componente sin montar. Esto es un no-op. Por favor, compruebe el código para el componente XXX.
Edición 2018 : ahora React tiene soporte de primera clase para el context
Intentaré dar una respuesta genérica, dirigida a este problema específico, pero también a un problema más general.
Si no te preocupan 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 la ventana cada vez.
En otros casos en los que desea crear un sitio web con capacidad de respuesta pero prefiere Reactuar estilos en línea a las consultas de medios, o desea que el comportamiento de HTML / JS cambie de acuerdo con el ancho de la ventana, siga leyendo:
¿Qué es el contexto React y por qué hablo de ello?
El contexto de reacción no está en la API pública y permite pasar propiedades a una jerarquía completa de componentes.
El contexto React es particularmente útil para pasar a toda la aplicación cosas que nunca cambian (es usado por muchos marcos de Flux a través de una mezcla). 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 utilizar para almacenar cosas que pueden cambiar. El problema es que cuando el contexto cambia, todos los componentes que lo usan deben volver a representarse y no es fácil hacerlo, la mejor solución a menudo es desmontar / volver a montar toda la aplicación con el nuevo contexto. Recuerda que forceUpdate no es recursivo .
Entonces, como usted entiende, el contexto es práctico, pero hay un impacto en el rendimiento cuando cambia, por lo que no debería cambiar demasiado a menudo.
Qué poner en contexto
- Invariantes: como el ID de usuario 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, a medida que se traduce la aplicación completa, tenemos que volver a renderizar todo: un excelente caso de cambio de idioma caliente.
Las propiedades de la ventana
El ancho y la altura no cambian a menudo, pero cuando lo hacemos, nuestro diseño y comportamiento pueden tener que adaptarse. Para el diseño, a veces es fácil personalizarlo con CSS mediaqueries, pero a veces no lo es y requiere una estructura HTML diferente. Para el comportamiento tienes que manejar esto con Javascript.
No desea volver a renderizar todo en cada evento de cambio de tamaño, por lo que debe rebotar los eventos de cambio de tamaño.
Lo que entiendo de su problema es que desea saber cuántos elementos mostrar según el ancho de la pantalla. Por lo tanto, primero debe definir los puntos de corte de respuesta y enumerar la cantidad de diferentes tipos de diseño que puede tener.
Por ejemplo:
- Diseño "1col", para ancho <= 600
- Diseño "2col", para 600 <ancho <1000
- Diseño "3col", para 1000 <= ancho
En los eventos de cambio de tamaño (con rebote), 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 tipo de diseño anterior, y si ha cambiado, vuelva a renderizar la aplicación con un nuevo contexto: esto permite evitar la representación de la aplicación cuando el usuario ha activado eventos de cambio de tamaño pero en realidad El tipo de diseño no ha cambiado, por lo que solo vuelve a renderizar cuando sea necesario.
Una vez que tenga eso, simplemente puede 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 ... Usted conoce su tipo de diseño dentro de la función de reacción React, por lo que significa que puede escribir de forma segura sitios web receptivos mediante el uso de estilos en línea y no necesita ninguna mediaqueries.
Si usa Flux, puede usar una tienda en lugar del contexto React, pero si su aplicación tiene muchos componentes sensibles, ¿tal vez sea más sencillo usar el contexto?