当浏览器窗口调整大小时,如何让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”的方式来做到这一点?


当前回答

想分享这个很酷的东西,我用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返回,并使用它有条件地呈现一个组件,火灾行动等。

其他回答

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

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

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

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

否则你会得到警告:

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

2020年更新。对于认真关注性能的React开发人员。

上述解决方案确实有效,但只要窗口大小改变一个像素,就会重新渲染组件。

这通常会导致性能问题,所以我写了useWindowDimension钩子,在短时间内反弹调整大小事件。如100毫秒

import React, { useState, useEffect } from 'react';

export function useWindowDimension() {
  const [dimension, setDimension] = useState([
    window.innerWidth,
    window.innerHeight,
  ]);
  useEffect(() => {
    const debouncedResizeHandler = debounce(() => {
      console.log('***** debounced resize'); // See the cool difference in console
      setDimension([window.innerWidth, window.innerHeight]);
    }, 100); // 100ms
    window.addEventListener('resize', debouncedResizeHandler);
    return () => window.removeEventListener('resize', debouncedResizeHandler);
  }, []); // Note this empty array. this effect should run only on mount and unmount
  return dimension;
}

function debounce(fn, ms) {
  let timer;
  return _ => {
    clearTimeout(timer);
    timer = setTimeout(_ => {
      timer = null;
      fn.apply(this, arguments);
    }, ms);
  };
}

像这样使用它。

function YourComponent() {
  const [width, height] = useWindowDimension();
  return <>Window width: {width}, Window height: {height}</>;
}

import React, {useState} from 'react'; type EventListener = () => void let eventListener: EventListener | undefined; function setEventListener(updateSize: (size: number[]) => void){ if(eventListener){ window.removeEventListener('resize',eventListener); } eventListener = () => updateSize([window.innerWidth, window.innerHeight]); return eventListener as EventListener; } function setResizer(updateSize: (size: number[]) => void) { window.addEventListener( 'resize', setEventListener(updateSize) ); } function useWindowSizeTableColumns() { const [size, setSize] = useState([ window.innerWidth || 0, window.innerHeight || 0 ]); setResizer(updateSize); return size; function updateSize(s: number[]) { if(size.some((v, i) => v !== s[i])){ setSize(s); } } } export default useWindowSize;

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

你不一定需要强制重新渲染。

这可能对OP没有帮助,但在我的情况下,我只需要更新画布上的宽度和高度属性(这是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;
`