ブラウザウィンドウのサイズが変更されたときにReactにビューを再レンダリングさせるにはどうすればよいですか。
ページ上に個別にレイアウトしたいブロックがありますが、ブラウザウィンドウが変更されたときにも更新したいと思います。最終的な結果は、 Ben Hollandの Pinterestのレイアウトのようになりますが、jQueryだけでなくReactを使って書かれています。私はまだ道を進んでいます。
これが私のアプリです。
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
コンポーネントを持っています(上記のPinterestの例のPin
に相当します):
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>
);
}
});
そして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>
);
}
});
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);
}
});
@SophieAlpertは正しい、+ 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)
}
これは、jQueryを使わずにes6を使う簡単で短い例です。
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>
);
}
}
編集2018年:現在Reactは コンテキストを最初のクラスでサポートしています
私はこの特定の問題を対象とした一般的な答えを出してみますが、より一般的な問題でもあります。
副作用ライブラリを気にしないのなら、 Packery のようなものを使うことができます。
Fluxを使うのであれば、ウィンドウオブジェクトを含むストアを作成して、毎回ウィンドウオブジェクトを問い合わせる必要なしに純粋なレンダリング機能を維持することができます。
レスポンシブWebサイトを構築したいが、メディアクエリよりもインラインスタイルを反応させたい場合、またはウィンドウ幅に応じてHTML/JSの動作を変更したい場合は、次のように続けてください。
Reactコンテキストとは何か、そしてなぜそれについて話しますか
リアクトコンテキスト anはパブリックAPIにはなく、コンポーネントの階層全体にプロパティを渡すことを許可します。
リアクトコンテキストは、アプリケーション全体に変更されないものを渡すために特に便利です(ミックスインを通じて多くのFluxフレームワークで使用されます)。あなたはアプリのビジネスの不変条件を保存するためにそれを使うことができます(接続されたuserIdのように、それはどこでも利用可能です)。
しかし、それは変化する可能性のあるものを保管するためにも使用できます。問題は、コンテキストが変更されたときにそれを使用するすべてのコンポーネントを再レンダリングする必要があり、変更が容易ではないことです。最善の解決策は、アプリ全体を新しいコンテキストでアンマウント/再マウントすることです。 forceUpdateは再帰的ではありません 。
あなたが理解するように、文脈は実用的です、しかし、それが変わるときパフォーマンスへの影響があるので、それはむしろ頻繁に変わるべきではありません。
文脈に入れるべきもの
これはあまり変わらないことです。
現在のユーザー言語:
それはそれほど頻繁には変更されません、そしてそれが変更されたとき、アプリ全体が翻訳されるとき、私たちはすべてを再レンダリングしなければなりません:熱い言語変更のとても素敵なユースケース
ウィンドウのプロパティ
幅と高さはあまり変わらないが、レイアウトや振る舞いをするときには適応する必要があるかもしれない。レイアウトについては、CSSのメディアクエリを使用してカスタマイズするのが簡単な場合もありますが、そうでない場合もあり、別のHTML構造が必要になる場合もあります。動作のためにはJavascriptでこれを処理しなければなりません。
すべてのサイズ変更イベントですべてを再レンダリングする必要はないので、サイズ変更イベントをデバウンスする必要があります。
あなたの問題について私が理解していることは、あなたがあなたがスクリーンの幅に従って何個のアイテムを表示するべきか知りたいということです。それで、あなたは最初に レスポンシブブレークポイント を定義し、あなたが持つことができる異なるレイアウトタイプの数を列挙しなければなりません。
例えば:
サイズ変更イベント(デバウンス)では、ウィンドウオブジェクトを照会することで現在のレイアウトタイプを簡単に取得できます。
次に、レイアウトタイプを以前のレイアウトタイプと比較し、変更されている場合は新しいコンテキストでアプリを再レンダリングします。これにより、ユーザーがサイズ変更イベントをトリガーしたが実際にはレイアウトタイプは変更されていないので、必要なときにのみ再レンダリングします。
それができれば、HTML、動作、CSSクラスなどをカスタマイズできるように、アプリケーション内のレイアウトタイプを(コンテキストからアクセス可能に)使用することができます。インラインスタイルを使用してレスポンシブWebサイトを安全に作成できます。また、mediaqueriesはまったく必要ありません。
Fluxを使えば、Reactコンテキストの代わりにstoreを使うことができますが、あなたのアプリにレスポンシブコンポーネントがたくさんあるなら、contextを使うほうが簡単かもしれません。
私は@senornestorの解決策を使用しますが、完全に正しくするためにはイベントリスナも削除する必要があります。
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount(){
window.removeEventListener('resize', this.handleResize);
}
handleResize = () => {
this.forceUpdate();
};
それ以外の場合は、警告が表示されます。
Warning:forceUpdate(...):マウントされているコンポーネントまたはマウントされているコンポーネントのみを更新できます。これは通常、マウント解除されたコンポーネントに対してforceUpdate()を呼び出したことを意味します。これは何もしないことです。 XXXコンポーネントのコードを確認してください。
このコードは新しい ReactコンテキストAPI を使用しています。
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>;
}
}
それから、このようにあなたのコードのどこにでもあなたはそれを使うことができます:
<WindowConsumer>
{({ width, height }) => //do what you want}
</WindowConsumer>
上記の答えをすべてスキップして、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
必ずしも再レンダリングを強制する必要はありません。
これは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;
`
これが最善のアプローチであるかどうかはわかりませんが、最初にストアを作成したのが私にとって効果的だったので、私はそれを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());
}
}));
参考までに、これらのファイルは部分的にトリミングされています。
React 16.8以降では フック を使用できます。
/* 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}</>
}
私はこれが答えられたことを知っています、しかし私が私の解決策を一番の答えとして共有することを考えていました、素晴らしいけれども、今は少し時代遅れかもしれません。
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 })
}
唯一の違いは、パフォーマンスを少し向上させるためにresizeイベントでupdateWindowDimensionsをデビューすることです(ただし、200msごとに実行されます)。ただし、ComponentDidMountで呼び出されたときはデビューしません。
マウントが頻繁に行われるような状況にある場合は、デバウンスによってマウントが時々かなり遅れることがわかりました。
ほんの少しの最適化ですが、それが誰かに役立つことを願っています!
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();
};
サイズ変更イベント機能を定義する必要があるだけです。
次に、レンダラーのサイズ(キャンバス)を更新し、カメラに新しい縦横比を割り当てます。
私の考えでは、マウントを解除して再ルーティングすることは狂気の解決策です。
必要に応じて以下がマウントです。
<div
className={this.state.canvasActive ? 'canvasContainer isActive' : 'canvasContainer'}
ref={mount => {
this.mount = mount;
}}
/>
https://github.com/renatorib/react-sizes は、依然として良好なパフォーマンスを維持しながらこれを実行するためのHOCです。
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
forceUpdate
を使用する@senornestorのソリューションと、コンポーネントのマウント解除時にresize
イベントリスナーを削除する@gkriのソリューションを改善するだけです。
bind(this)
を確認してください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>
)
}
}
別の方法は、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>
)
}
}
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
は、ウィンドウが指定されたmax-width値よりも大きいまたは小さい場合にtrueまたはfalseを返します。これは、ブール値が変更されたときにmatchMediaが1回だけ起動するため、リスナーを調整する必要がないことを意味します.
useState
を含むようにコードを簡単に調整して、ブール型matchMediaの戻り値を保存し、それを使用して条件付きでコンポーネントをレンダリングしたり、アクションを起動したりできます。
答えてくれてありがとう。これが私のReact + 再構成 です。これは、コンポーネントに対するwindowHeight
およびwindowWidth
プロパティを含む高階関数です。
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');
}})
)
このため、CSSまたはJSONファイルのデータからこのデータを使用し、次にこのデータを使用してthis.state({width: "some value"、height: "some value"})で新しい状態を設定するとより効果的です。レスポンシブ画像を表示する場合は、幅のある画面データのデータを自作で使用するコードを作成します。
Classシンタックスで動作させるためにコンストラクタ内で '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)
}
}