Как я могу получить React для повторного отображения представления при изменении размера окна браузера?
У меня есть некоторые блоки, которые я хочу разделить на странице отдельно, однако я также хочу, чтобы они обновлялись при изменении окна браузера. Самый конечный результат будет чем-то вроде , который будет написан на английском языке, но написанный с использованием React не только jquery. Я все еще далеко.
Здесь мое приложение:
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')
)
Тогда у меня есть компонент Block (эквивалент Pin в приведенном выше примере Pinterest):
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>
);
}
});
и список/набор блоков:
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>
);
}
});
Должен ли я добавить размер окна jquery, если да, где?
$( window ).resize(function() {
// re-render the component
});
Есть ли способ "Реакт" сделать это?
Вы можете прослушивать компонент componentDidMount, похожий на этот компонент, который просто отображает размеры окна (например, <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);
}
});
@BenAlpert прав, +1, я просто хочу предоставить модифицированную версию его решения без jQuery на основе этого ответа.
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);
}
});
Очень простое решение:
resize = () => this.forceUpdate()
componentDidMount() {
window.addEventListener('resize', this.resize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize)
}
componentWillUnmount()
!
Это простой и короткий пример использования es6 без jQuery.
class CreateContact extends Component{
state = {
}
constructor(props) {
super(props);
this.state = {
windowHeight: window.innerHeight,
windowWidth: window.innerWidth
};
}
handleResize(e) {
this.setState({
windowHeight: window.innerHeight,
windowWidth: window.innerWidth
});
}
componentDidMount() {
window.addEventListener('resize', ::this.handleResize)
}
componentWillUnmount() {
window.removeEventListener('resize', ::this.handleResize)
}
render() {
return (
<span>
{this.state.windowWidth} x {this.state.windowHeight}
</span>
);
}
}
::
bind возвращает новое значение каждый раз, когда оно применяется. Таким образом, ваш прослушиватель событий не будет фактически незарегистрированным, так как ваш removeEventListener
заканчивает тем, что передает функцию, отличную от той, которая была первоначально передана addEventListener
.
Я попытаюсь дать общий ответ, который нацелен на эту конкретную проблему, но и на более общую проблему.
Если вам не нужны библиотеки побочных эффектов, вы можете просто использовать что-то вроде Packery
Если вы используете Flux, вы можете создать хранилище, содержащее свойства окна, чтобы сохранить чистую функцию рендеринга без необходимости запрашивать объект окна каждый раз.
В других случаях, когда вы хотите создать отзывчивый веб-сайт, но предпочитаете использовать встроенные стили для медиа-запросов или хотите, чтобы поведение HTML/JS изменялось в соответствии с шириной окна, продолжайте читать:
Что такое контекст React и почему я об этом говорю
Реагировать на контекст, не входит в открытый API и позволяет передавать свойства целой иерархии компонентов.
Конкретный контекст особенно полезен для передачи всем вашим приложениям вещей, которые никогда не меняются (он используется многими флюсами Flux через mixin). Вы можете использовать его для хранения бизнес-инвариантов приложения (например, подключенного пользователя, так что он доступен везде).
Но он также может использоваться для хранения вещей, которые могут измениться. Проблема заключается в том, что при изменении контекста все компоненты, которые его используют, должны быть повторно отображены, и это непросто сделать, лучшее решение часто заключается в том, чтобы размонтировать/перемонтировать все приложение с новым контекстом. Помните forceUpdate не рекурсивный.
Итак, как вы понимаете, контекст практичен, но влияние производительности влияет на его изменение, поэтому оно не должно меняться слишком часто.
Что добавить в контекст
Вот что часто не меняется:
Текущий язык пользователя:
Это не меняется очень часто, и когда это происходит, так как все приложение переведено, мы должны повторно отобразить все: очень приятная утилита изменения горячего langage
Свойства окна
Ширина и высота не часто меняются, но когда мы делаем макет и поведение, возможно, придется адаптироваться. Для макета иногда легко настраивать с помощью CSS mediaqueries, но иногда это не так и требует другой структуры HTML. Для поведения вы должны обрабатывать это с помощью Javascript.
Вы не хотите повторно отображать все в каждом событии изменения размера, поэтому вам нужно отменить события изменения размера.
Что я понимаю о вашей проблеме, так это то, что вы хотите узнать, сколько элементов отображать в соответствии с шириной экрана. Таким образом, вы должны сначала определить отзывчивые точки останова и перечислить количество различных типов макетов, которые вы можете иметь.
Например:
При изменении размера событий (debounced) вы можете легко получить текущий тип макета, запросив объект окна.
Затем вы можете сравнить тип макета с прежним типом макета, и если он изменился, переопределите приложение с новым контекстом: это позволяет избежать повторного рендеринга приложения вообще, когда пользователь имеет триггер изменения размера событий но на самом деле тип макета не изменился, поэтому вы только повторно визуализируете при необходимости.
После этого вы можете просто использовать тип макета внутри вашего приложения (доступный через контекст), чтобы вы могли настраивать HTML, поведение, классы CSS... Вы знаете свой тип макета внутри функции рендеринга React, поэтому это означает, что вы можете безопасно писать отзывчивые веб-сайты с помощью встроенных стилей и вообще не нуждаться в медиа-материалах.
Если вы используете Flux, вы можете использовать хранилище вместо контекста React, но если ваше приложение имеет множество гибких компонентов, возможно, проще использовать контекст?
Я использую решение @senornestor, но, чтобы быть полностью правильным, вам нужно также удалить прослушиватель событий:
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount(){
window.removeEventListener('resize', this.handleResize);
}
handleResize = () => {
this.forceUpdate();
};
В противном случае вы получите предупреждение:
Предупреждение: forceUpdate (...): может обновлять только установленный или монтируемый компонент. Обычно это означает, что вы вызвали forceUpdate() на unmounted компонент. Это не-op. Пожалуйста, проверьте код для XXX компонент.
Я бы пропустил все приведенные выше ответы и начал использовать компонент react-dimensions
более высокого порядка.
https://github.com/digidem/react-dimensions
Просто добавьте простой import
и вызов функции, и вы можете получить доступ к this.props.containerWidth
и this.props.containerHeight
в своем компоненте.
// 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
react-dimensions
работает даже с программно измененными размерами (например, изменился размер элемента div, который влияет на размер вашего контейнера, поэтому вам необходимо обновить свой размер). Ответ Бена Альперта помогает только при изменении размера окна браузера. Смотрите здесь: github.com/digidem/react-dimensions/issues/4
Не уверен, что это лучший подход, но то, что сработало для меня, сначала создало Store, я назвал его 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;
Затем я использовал этот магазин в своих компонентах, вроде этого:
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, эти файлы были частично обрезаны.
Вам необязательно принудительно повторять рендер.
Это может не помочь OP, но в моем случае мне нужно было обновить атрибуты width
и height
на моем холсте (которые вы не можете сделать с CSS).
Он выглядит следующим образом:
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;
`
Привязать его к 'this' в конструкторе, чтобы заставить его работать с синтаксисом класса
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)
}
}
Спасибо всем за ответы. Здесь мой React + Recompose. Это функция высокого порядка, которая включает свойства windowHeight и windowWidth для компонента.
const withDimentions = 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');
}})
)
this.updateDimensions
передаваемыйaddEventListener
является просто пустой ссылкой на функцию, которая не будет иметь значения дляthis
при вызове. Следует ли использовать анонимную функцию или вызов .bind (), чтобы добавитьthis
, или я неправильно понял?this
для любых методов, определенных непосредственно в компоненте.