使用 React 调整浏览器大小的重新渲染视图

IT技术 javascript reactjs resize
2021-01-13 06:52:27

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

背景

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

代码

这是我的应用程序:

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组件(相当于Pin上面 Pinterest 示例中的 a):

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

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

6个回答

使用 React 钩子:

您可以定义一个自定义 Hook 来监听窗口resize事件,如下所示:

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

function useWindowSize() {
  const [size, setSize] = useState([0, 0]);
  useLayoutEffect(() => {
    function updateSize() {
      setSize([window.innerWidth, window.innerHeight]);
    }
    window.addEventListener('resize', updateSize);
    updateSize();
    return () => window.removeEventListener('resize', updateSize);
  }, []);
  return size;
}

function ShowWindowDimensions(props) {
  const [width, height] = useWindowSize();
  return <span>Window size: {width} x {height}</span>;
}

这里的好处是逻辑被封装了,你可以在任何你想使用窗口大小的地方使用这个 Hook。

使用 React 类:

您可以在 componentDidMount 中侦听,类似于这个只显示窗口尺寸的组件(如<span>Window size: 1024 x 768</span>):

import React from 'react';

class ShowWindowDimensions extends React.Component {
  state = { width: 0, height: 0 };
  render() {
    return <span>Window size: {this.state.width} x {this.state.height}</span>;
  }
  updateDimensions = () => {
    this.setState({ width: window.innerWidth, height: window.innerHeight });
  };
  componentDidMount() {
    window.addEventListener('resize', this.updateDimensions);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.updateDimensions);
  }
}
@MattDell 是的,ES6 类只是普通类,因此不会与它们自动绑定。
2021-03-21 06:52:27
这是如何运作的?this.updateDimensions传给addEventListener只是一个光秃秃的函数引用这将对没有valuethis时调用。应该使用匿名函数或 .bind() 调用来添加this,还是我误解了?
2021-03-29 06:52:27
不需要 jQuery - 使用innerHeightinnerWidth来自window. componentWillMount如果您使用getInitialStatesetheight和 ,可以跳过width
2021-03-30 06:52:27
@MattDell 看起来::绑定语法现在已经过时了sitepoint.com/bind-javascripts-this-keyword-react “绑定操作符 (::) 不会成为 ES7 的一部分,因为 ES7 功能集在二月份被冻结了,绑定操作符是 ES8 的提议"
2021-04-06 06:52:27
@chrisdew 我在这里有点晚了,但是 React 会自动绑定this直接在组件上定义的任何方法。
2021-04-09 06:52:27

@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);
    }
});
真的还有人关心IE8吗?还是只是习惯?
2021-03-12 06:52:27
@andrerpena caniuse.com/#search=addeventlistener表示 ie8 会有问题
2021-03-25 06:52:27
仅当您为 vanilla JS 事件侦听器设置了与 IE 相关的 polyfill 时才有效
2021-03-28 06:52:27
@nnnn 你能详细说明一下吗?我承认我只在 Chrome 中测试过这个。你是说 window.addEventListener 在没有 polyfills 的情况下不能在 IE 上工作吗?
2021-04-01 06:52:27
@nnnn。我知道了。是的..所以我的解决方案在 IE 8 上不起作用,但从 9 开始工作:)。谢谢。
2021-04-07 06:52:27

一个非常简单的解决方案:

resize = () => this.forceUpdate()

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

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
不要忘记限制强制更新,否则它会看起来非常小故障。
2021-03-13 06:52:27
不是在每个像素上运行调整大小函数,而是在一些短周期时间内运行它,以产生流动性的错觉,您总是处理第一个和最后一个事件,从而给人一种它正在流畅地处理调整大小的感觉。
2021-03-25 06:52:27
如果它受到限制,这是最好的解决方案。我的意思是如果forceUpdate有条件地应用
2021-03-29 06:52:27
需要限制的不是 forceUpdate(被调用的东西),而是需要限制的调整大小事件触发(触发的东西)。当您将窗口从大到小调整大小时,从技术上讲,可以在每个像素上调用调整大小事件。当用户快速执行此操作时,事件比您关心的要多。更糟糕的是,您将 UI 线程绑定到 Javascript 线程意味着您的应用程序将开始变得非常缓慢,因为它试图单独处理每个事件。
2021-04-02 06:52:27
也不要忘记删除侦听器componentWillUnmount()
2021-04-11 06:52:27

这是一个不使用 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>
    );
  }
}

钩子

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

let App = () => {
  const [windowWidth, setWindowWidth] = useState(0);
  const [windowHeight, setWindowHeight] = useState(0);
  let resizeWindow = () => {
    setWindowWidth(window.innerWidth);
    setWindowHeight(window.innerHeight);
  };

  useEffect(() => {
    resizeWindow();
    window.addEventListener("resize", resizeWindow);
    return () => window.removeEventListener("resize", resizeWindow);
  }, []);

  return (
    <div>
      <span>
        {windowWidth} x {windowHeight}
      </span>
    </div>
  );
};
这是一个不错的简洁答案,但 AFAICT 有一个错误:除非我弄错了,否则::每次应用绑定运算符时都会返回一个新值。因此,您的事件侦听器实际上不会被取消注册,因为您removeEventListener最终传递的函数与最初传递给的函数不同addEventListener.
2021-04-09 06:52:27

从 React 16.8 开始,您可以使用Hooks

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