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


当前回答

对于那些寻求Typescript支持的人,我根据@Lead Developer的回答写了下面的钩子:

import { useState, useEffect } from 'react';


export const debounce = <A extends unknown[]>(callback: (...args: A) => unknown, msDelay: number) => {
    let timer: NodeJS.Timeout | undefined;

    return (...args: A) => {
        clearTimeout(timer);

        timer = setTimeout(() => {
            timer = undefined;
            callback(...args);
        }, msDelay);
    };
};

export const useWindowDimension = (msDelay = 100) => {
    const [dimension, setDimension] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        const resizeHandler = () => {
            setDimension({
                width: window.innerWidth,
                height: window.innerHeight
            });
        };

        const handler = msDelay <= 0 ? resizeHandler : debounce(resizeHandler, msDelay);

        window.addEventListener('resize', handler);

        return () => window.removeEventListener('resize', handler);
    }, []);

    return dimension;
};

export type Dimension = ReturnType<typeof useWindowDimension>;

很明显,它的好处是Typescript支持,还有一个选项可以控制延迟时间(以毫秒为单位),默认为100ms。如果延迟时间为0或更少,它将不会反弹处理程序。 另一个区别是钩子返回一个Dimension类型的对象,即:

type Dimension = {
    width: number;
    height: number;
}

这样,每个人都清楚钩子实际返回的是什么。

其他回答

必须将它绑定到构造函数中的“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)
  }
}

我知道这个问题已经有人回答了,但我只是想把我的解决方案作为最重要的答案分享给大家,虽然很好,但现在可能有点过时了。

    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事件上deboundation(只运行每200ms一次)updateWindowDimensions来增加一点性能,但不是在ComponentDidMount上调用时deboundation它。

我发现,如果你经常安装它,那么反弹有时会使它的安装相当滞后。

只是一个小的优化,但希望它能帮助到别人!

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