web-dev-qa-db-ja.com

Reactを使用してブラウザのサイズ変更をレンダリングする

ブラウザウィンドウのサイズが変更されたときに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
});

これを行うためのより「正確な」方法はありますか?

238
digibake

あなたは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);
    }
});
375
Sophie Alpert

@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);
    }
});
116
André Pena

非常に簡単な解決策:

resize = () => this.forceUpdate()

componentDidMount() {
  window.addEventListener('resize', this.resize)
}

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
39
senornestor

これは、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>
    );
  }
}
30
voice

編集2018年:現在Reactは コンテキストを最初のクラスでサポートしています


私はこの特定の問題を対象とした一般的な答えを出してみますが、より一般的な問題でもあります。

副作用ライブラリを気にしないのなら、 Packery のようなものを使うことができます。

Fluxを使うのであれば、ウィンドウオブジェクトを含むストアを作成して、毎回ウィンドウオブジェクトを問い合わせる必要なしに純粋なレンダリング機能を維持することができます。

レスポンシブWebサイトを構築したいが、メディアクエリよりもインラインスタイルを反応させたい場合、またはウィンドウ幅に応じてHTML/JSの動作を変更したい場合は、次のように続けてください。

Reactコンテキストとは何か、そしてなぜそれについて話しますか

リアクトコンテキスト anはパブリックAPIにはなく、コンポーネントの階層全体にプロパティを渡すことを許可します。

リアクトコンテキストは、アプリケーション全体に変更されないものを渡すために特に便利です(ミックスインを通じて多くのFluxフレームワークで使用されます)。あなたはアプリのビジネスの不変条件を保存するためにそれを使うことができます(接続されたuserIdのように、それはどこでも利用可能です)。

しかし、それは変化する可能性のあるものを保管するためにも使用できます。問題は、コンテキストが変更されたときにそれを使用するすべてのコンポーネントを再レンダリングする必要があり、変更が容易ではないことです。最善の解決策は、アプリ全体を新しいコンテキストでアンマウント/再マウントすることです。 forceUpdateは再帰的ではありません

あなたが理解するように、文脈は実用的です、しかし、それが変わるときパフォーマンスへの影響があるので、それはむしろ頻繁に変わるべきではありません。

文脈に入れるべきもの

  • 不変式:接続されたuserId、sessionTokenのように、何でも...
  • あまり変わらないこと

これはあまり変わらないことです。

現在のユーザー言語

それはそれほど頻繁には変更されません、そしてそれが変更されたとき、アプリ全体が翻訳されるとき、私たちはすべてを再レンダリングしなければなりません:熱い言語変更のとても素敵なユースケース

ウィンドウのプロパティ

幅と高さはあまり変わらないが、レイアウトや振る舞いをするときには適応する必要があるかもしれない。レイアウトについては、CSSのメディアクエリを使用してカスタマイズするのが簡単な場合もありますが、そうでない場合もあり、別のHTML構造が必要になる場合もあります。動作のためにはJavascriptでこれを処理しなければなりません。

すべてのサイズ変更イベントですべてを再レンダリングする必要はないので、サイズ変更イベントをデバウンスする必要があります。

あなたの問題について私が理解していることは、あなたがあなたがスクリーンの幅に従って何個のアイテムを表示するべきか知りたいということです。それで、あなたは最初に レスポンシブブレークポイント を定義し、あなたが持つことができる異なるレイアウトタイプの数を列挙しなければなりません。

例えば:

  • 幅 "= 600のレイアウト" 1col "
  • 600 <幅<1000のレイアウト "2col"
  • 1000 <=幅のレイアウト "3col"

サイズ変更イベント(デバウンス)では、ウィンドウオブジェクトを照会することで現在のレイアウトタイプを簡単に取得できます。

次に、レイアウトタイプを以前のレイアウトタイプと比較し、変更されている場合は新しいコンテキストでアプリを再レンダリングします。これにより、ユーザーがサイズ変更イベントをトリガーしたが実際にはレイアウトタイプは変更されていないので、必要なときにのみ再レンダリングします。

それができれば、HTML、動作、CSSクラスなどをカスタマイズできるように、アプリケーション内のレイアウトタイプを(コンテキストからアクセス可能に)使用することができます。インラインスタイルを使用してレスポンシブWebサイトを安全に作成できます。また、mediaqueriesはまったく必要ありません。

Fluxを使えば、Reactコンテキストの代わりにstoreを使うことができますが、あなたのアプリにレスポンシブコンポーネントがたくさんあるなら、contextを使うほうが簡単かもしれません。

14

私は@senornestorの解決策を使用しますが、完全に正しくするためにはイベントリスナも削除する必要があります。

componentDidMount() {
    window.addEventListener('resize', this.handleResize);
}

componentWillUnmount(){
    window.removeEventListener('resize', this.handleResize);
}

handleResize = () => {
    this.forceUpdate();
};

それ以外の場合は、警告が表示されます。

Warning:forceUpdate(...):マウントされているコンポーネントまたはマウントされているコンポーネントのみを更新できます。これは通常、マウント解除されたコンポーネントに対してforceUpdate()を呼び出したことを意味します。これは何もしないことです。 XXXコンポーネントのコードを確認してください。

9
gkri

このコードは新しい 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>
6
Albert Olivé

上記の答えをすべてスキップして、react-dimensions高次コンポーネントを使い始めます。

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

単純なimportと関数呼び出しを追加するだけで、コンポーネント内のthis.props.containerWidththis.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
6
MindJuice

必ずしも再レンダリングを強制する必要はありません。

これは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;
`
6
mpen

これが最善のアプローチであるかどうかはわかりませんが、最初にストアを作成したのが私にとって効果的だったので、私はそれを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());
    }
}));

参考までに、これらのファイルは部分的にトリミングされています。

5
David Sinclair

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}</>
}
5
Alex

私はこれが答えられたことを知っています、しかし私が私の解決策を一番の答えとして共有することを考えていました、素晴らしいけれども、今は少し時代遅れかもしれません。

    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で呼び出されたときはデビューしません。

マウントが頻繁に行われるような状況にある場合は、デバウンスによってマウントが時々かなり遅れることがわかりました。

ほんの少しの最適化ですが、それが誰かに役立つことを願っています!

4
Matt Wills
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;
                }}
            />
2
Nicolay Hekkens

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
1
Russell Cohen

forceUpdateを使用する@senornestorのソリューションと、コンポーネントのマウント解除時にresizeイベントリスナーを削除する@gkriのソリューションを改善するだけです。

  1. サイズ変更の呼び出しを調整(またはバウンス解除)することを忘れないでください
  2. コンストラクターで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>
    )
  }
}
1
kimbaudi

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の戻り値を保存し、それを使用して条件付きでコンポーネントをレンダリングしたり、アクションを起動したりできます。

1
Riley Brown

答えてくれてありがとう。これが私の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');
 }})
)
1
dumorango

このため、CSSまたはJSONファイルのデータからこのデータを使用し、次にこのデータを使用してthis.state({width: "some value"、height: "some value"})で新しい状態を設定するとより効果的です。レスポンシブ画像を表​​示する場合は、幅のある画面データのデータを自作で使用するコードを作成します。

0

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)
  }
}
0
Jim Perris