当浏览器窗口调整大小时,如何让React重新渲染视图?

背景

我有一些块,我想在页面上单独布局,但我也希望他们更新时,浏览器窗口的变化。最终的结果将是像Ben Holland的Pinterest布局,但使用React而不仅仅是jQuery。我还有一段路要走。

Code

这是我的应用程序:

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

和block的列表/集合:

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

有没有更“React”的方式来做到这一点?


当前回答

只是为了改进@senornestor使用forceUpdate的解决方案和@gkri在组件卸载时删除resize事件监听器的解决方案:

不要忘记抑制(或撤消)调整大小的要求 确保在构造函数中绑定(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>
    )
  }
}

另一种方法是使用一个“dummy”状态来代替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>
    )
  }
}

其他回答

我将跳过上述所有答案,并开始使用反应维度的高阶组件。

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

只需添加一个简单的导入和一个函数调用,就可以在组件中访问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

我使用@senornestor的解决方案,但要完全正确,你必须删除事件监听器以及:

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

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

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

否则你会得到警告:

警告:forceUpdate(…):只能更新已挂载或正在挂载 组件。这通常意味着在卸载的对象上调用forceUpdate() 组件。这是不可能的。请查一下XXX的代码 组件。

由于这个原因,更好的是如果你使用这个数据来自CSS或JSON文件的数据,然后用这个数据设置新的状态。状态({width: "some value",height:"some value"});或者写代码谁使用宽屏幕数据的数据在自己的工作,如果你希望响应显示图像

想分享这个很酷的东西,我用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
    }
  };

如果窗口高于或低于指定的max-width值,.matches将返回true或false,这意味着不需要限制侦听器,因为matchMedia只在布尔值改变时触发一次。

我的代码可以很容易地调整到包括useState来保存布尔matchMedia返回,并使用它有条件地呈现一个组件,火灾行动等。

在index.js:

function render() {
  ReactDOM.render(<App />, document.getElementById('root'));
}

render();

window.addEventListener('resize', render);

重新渲染要求重新计算所有依赖于React本身无法检测到的变量的东西,比如window。innerWidth/innerHeight, localStorage等,同时保持应用程序状态不变。

如果需要在其他情况下重新渲染,也可以导出此render()函数并在其他地方使用。

虽然我不确定这对性能的影响(因为它可能会重新渲染所有内容或只是改变了什么),但对我来说,调整大小时它似乎足够快。