【问题标题】:Rerender view on browser resize with React使用 React 在浏览器上重新渲染视图调整大小
【发布时间】:2019-04-01 01:12:12
【问题描述】:

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

背景

我想在页面上单独布局一些块,但是我也希望它们在浏览器窗口更改时更新。最终结果将类似于Ben Holland's 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 组件(相当于上面 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
});

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

【问题讨论】:

    标签: javascript reactjs resize


    【解决方案1】:

    使用 React Hooks:

    您可以定义一个自定义 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 中监听,类似于这个仅显示窗口尺寸的组件(如 &lt;span&gt;Window size: 1024 x 768&lt;/span&gt;):

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

    【讨论】:

    • 这是如何工作的?传递给addEventListenerthis.updateDimensions 只是一个裸函数引用,在调用时对this 没有任何值。是否应该使用匿名函数或 .bind() 调用来添加 this,还是我误解了?
    • @chrisdew 我来晚了一点,但是 React 自动绑定 this 用于直接在组件上定义的任何方法。
    • @MattDell 是的,ES6 类只是普通类,因此不会自动绑定它们。
    • 不需要 jQuery - 使用来自windowinnerHeightinnerWidth。如果您使用getInitialState 设置heightwidth,则可以跳过componentWillMount
    • @MattDell 看起来 :: 绑定语法现在已经失效 sitepoint.com/bind-javascripts-this-keyword-react “绑定运算符 (::) 不会成为 ES7 的一部分,因为 ES7 功能集已被冻结2 月,bind 操作符是 ES8 的一项提案”
    【解决方案2】:

    @SophieAlpert 是对的,+1,我只是想提供她的解决方案的修改版本,没有 jQuery,基于this answer

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

    【讨论】:

    • 只有在你为 vanilla JS 事件监听器设置了与 IE 相关的 polyfill 时才有效
    • @nnnn 你能详细说明一下吗?我承认我只在 Chrome 中测试过这个。你是说 window.addEventListener 不能在没有 polyfill 的情况下在 IE 上工作?
    • @andrerpena caniuse.com/#search=addeventlistener 表示ie8会有问题
    • @nnnn。我知道了。是的.. 所以我的解决方案不适用于 IE 8,但适用于 9 :)。谢谢。
    • 真的还有人关心IE8吗?还是只是习惯?
    【解决方案3】:

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

    resize = () => this.forceUpdate()
    
    componentDidMount() {
      window.addEventListener('resize', this.resize)
    }
    
    componentWillUnmount() {
      window.removeEventListener('resize', this.resize)
    }
    

    【讨论】:

    • 不要忘记限制强制更新,否则它会看起来非常有问题。
    • 另外不要忘记删除componentWillUnmount()上的监听器!
    • 如果受到限制,这是最好的解决方案。我的意思是如果有条件地应用forceUpdate
    • 需要限制的不是 forceUpdate(被调用的东西),而是需要限制的 resize 事件触发(触发的东西)。当您将窗口从大到小调整大小时,从技术上讲,可以在每个像素处调用调整大小事件。当用户快速执行此操作时,这比您关心的事件更多。更糟糕的是,您将 UI 线程绑定到 Javascript 线程,这意味着您的应用将开始感到非常缓慢,因为它会尝试单独处理每个事件。
    • 不是在每个像素上运行调整大小功能,而是在一些较短的周期性时间运行它,以产生流动的错觉,您总是处理第一个和最后一个事件,从而给人一种流畅的感觉处理调整大小。
    【解决方案4】:

    这是一个不带 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 的函数不同。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      2020 年更新。适合认真关心性能的 React 开发人员。

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

      这通常会导致性能问题,因此我编写了 useWindowDimension 挂钩,可以在短时间内消除 resize 事件的抖动。例如 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}</>;
      }
      

      【讨论】:

      • 我使用相同的想法仅在调整大小事件跨越 CSS 断点边界时“触发”钩子,即从移动设备大小调整为表格或桌面大小。
      • 嗨@Seth,你能分享你的解决方案吗?我需要做同样的事情。谢谢
      【解决方案7】:

      Edit 2018:现在 React 对 context 有一流的支持


      我将尝试给出一个通用的答案,针对这个特定问题,但也针对更普遍的问题。

      如果你不关心副作用库,你可以简单地使用类似Packery

      如果你使用 Flux,你可以创建一个包含窗口属性的 store,这样你就可以保留一个纯渲染函数,而不必每次都查询窗口对象。

      如果您想构建一个响应式网站,但您更喜欢 React 内联样式而不是媒体查询,或者希望 HTML/JS 行为根据窗口宽度而改变,请继续阅读:

      什么是 React 上下文以及我为什么要谈论它

      React context 不在公共 API 中,它允许将属性传递给整个组件层次结构。

      React 上下文对于将永不改变的东西传递给整个应用程序特别有用(许多 Flux 框架通过 mixin 使用它)。您可以使用它来存储应用业务不变量(例如连接的 userId,以便在任何地方都可用)。

      但它也可以用来存储可以改变的东西。问题是当上下文发生变化时,所有使用它的组件都应该重新渲染,而且这样做并不容易,最好的解决方案通常是使用新的上下文卸载/重新安装整个应用程序。记住forceUpdate is not recursive

      所以正如您所理解的,上下文是实用的,但是当它发生变化时会对性能产生影响,所以它不应该太频繁地变化。

      上下文中的内容

      • 不变量:比如连接的 userId、sessionToken 等等……
      • 不经常改变的事情

      以下是不经常改变的事情:

      当前用户语言

      它不会经常更改,当它发生变化时,随着整个应用程序的翻译,我们必须重新渲染所有内容:一个非常好的热语言更改用例

      窗口属性

      宽度和高度不会经常变化,但是当我们做布局和行为时可能不得不适应。对于布局,有时使用 CSS 媒体查询很容易自定义,但有时却不是,需要不同的 HTML 结构。对于必须使用 Javascript 处理的行为。

      您不想在每次调整大小事件时重新渲染所有内容,因此您必须对调整大小事件进行去抖动。

      我对您的问题的理解是您想知道根据屏幕宽度显示多少项目。所以你必须首先定义responsive breakpoints,并列举你可以拥有的不同布局类型的数量。

      例如:

      • 布局“1col”,宽度
      • 布局“2col”,适用于 600
      • 布局“3col”,1000

      在调整大小事件(去抖动)时,您可以通过查询窗口对象轻松获取当前布局类型。

      然后您可以将布局类型与之前的布局类型进行比较,如果它发生了变化,则使用新的上下文重新渲染应用程序:这可以避免在用户触发调整大小事件时重新渲染应用程序但实际上布局类型并没有改变,所以你只需要在需要时重新渲染。

      一旦你有了它,你就可以简单地在你的应用程序中使用布局类型(可通过上下文访问),这样你就可以自定义 HTML、行为、CSS 类......你知道你在 React 渲染函数中的布局类型,所以这意味着您可以使用内联样式安全地编写响应式网站,并且根本不需要媒体查询。

      如果你使用 Flux,你可以使用 store 代替 React 上下文,但如果你的应用有很多响应式组件,使用上下文可能更简单?

      【讨论】:

        【解决方案8】:

        我使用 @senornestor 的解决方案,但要完全正确,您还必须删除事件侦听器:

        componentDidMount() {
            window.addEventListener('resize', this.handleResize);
        }
        
        componentWillUnmount(){
            window.removeEventListener('resize', this.handleResize);
        }
        
        handleResize = () => {
            this.forceUpdate();
        };
        

        否则你会收到警告:

        警告:forceUpdate(...): 只能更新挂载或挂载 零件。这通常意味着您在未安装的设备上调用 forceUpdate() 零件。这是一个无操作。请检查XXX的代码 组件。

        【讨论】:

        • 您收到警告是有原因的:您所做的是不好的做法。你的 render() 函数应该是 props 和 state 的纯函数。在您的情况下,您应该将新尺寸保存在该州。
        【解决方案9】:

        我会跳过以上所有答案并开始使用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
        

        【讨论】:

        • 这并没有给出窗口的大小,只是给出了容器。
        • 是的,这就是重点。窗口的大小很容易找到。容器的大小更难找到,并且对于 React 组件更有用。 react-dimensions 的最新版本甚至适用于以编程方式更改的尺寸(例如,更改了 div 的大小,这会影响容器的大小,因此您需要更新大小)。 Ben Alpert 的回答仅有助于浏览器窗口调整大小事件。见这里:github.com/digidem/react-dimensions/issues/4
        • react-dimensions 处理窗口大小的变化、flexbox 布局的变化以及由于 JavaScript 调整大小而发生的变化。我认为这涵盖了它。你有没有处理不当的例子?
        • 窗口大小很容易获得。不需要图书馆:window.innerWidthwindow.innerHeightreact-dimensions 解决了问题中更重要的部分,并且在调整窗口大小时(以及容器大小变化时)也会触发您的布局代码。
        • 这个项目不再被积极维护。
        【解决方案10】:

        此代码使用新的React context 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>
        

        【讨论】:

          【解决方案11】:

          不确定这是否是最好的方法,但对我有用的是首先创建一个商店,我称之为 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;
          

          然后我在我的组件中使用了那个 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());
              }
          }));
          

          仅供参考,这些文件已部分修剪。

          【讨论】:

          • 存储状态应该只在响应分派的操作时改变。这绝对不是一个好方法。
          • @frostymarvelous 确实,也许更好地重新定位到其他答案中的组件中。
          • @DavidSinclair 或者更好的是,onresize 可以发送 WindowResizedAction
          • 这太过分了。
          【解决方案12】:

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

          这可能对 OP 没有帮助,但就我而言,我只需要更新画布上的 widthheight 属性(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;
          `
          

          【讨论】:

            【解决方案13】:

            我知道这已得到解答,但我只是想将我的解决方案作为最佳答案分享,虽然很好,但现在可能有点过时了。

                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 进行去抖动(仅每 200 毫秒运行一次)以提高性能,但在 ComponentDidMount 上调用它时不会去抖动。

            我发现如果您遇到经常挂载的情况,去抖动有时会使挂载变得相当滞后。

            只是一个小的优化,但希望它可以帮助某人!

            【讨论】:

            • initUpdateWindowDimensions 方法的定义在哪里?
            • 它是在构造函数中定义的 :) 它只是绑定到组件的 updateWindowDimensions 但没有去抖动。
            【解决方案14】:

            想分享我刚刚使用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 将在窗口高于或低于指定的最大宽度值时返回 true 或 false,这意味着无需限制侦听器,因为 matchMedia 仅在布尔值更改时触发一次。

            我的代码可以轻松调整为包含 useState 以保存布尔 matchMedia 返回,并使用它有条件地渲染组件、触发动作等。

            【讨论】:

            • 当我想将特定样式应用于窗体输入组件时,它会非常有用,因为它会影响窗口大小的范围,以避免它们的宽度变得太长或太短。谢谢!
            • 非常好的解决方案。你说的很酷!
            • 甜美干净。一路走来
            【解决方案15】:
            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();
            };
            

            只需要定义resize事件函数。

            然后更新渲染器大小(画布),为相机分配一个新的纵横比。

            在我看来,卸载和远程处理是一个疯狂的解决方案....

            如果需要,下面是安装。

                        <div
                            className={this.state.canvasActive ? 'canvasContainer isActive' : 'canvasContainer'}
                            ref={mount => {
                                this.mount = mount;
                            }}
                        />
            

            【讨论】:

              【解决方案16】:

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

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

              【讨论】:

                【解决方案17】:

                必须将它绑定到构造函数中的“this”才能使用 Class 语法

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

                【讨论】:

                  【解决方案18】:

                  谢谢大家的回答。这是我的反应 + Recompose。这是一个高阶函数,包含组件的windowHeightwindowWidth 属性。

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

                  【讨论】:

                    【解决方案19】:

                    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
                    

                    【讨论】:

                      【解决方案20】:

                      因此,如果您使用来自 CSS 或 JSON 文件数据的数据,然后使用此数据设置新状态,则更好的是 this.state({width: "some value",height:"some value" });如果您希望响应式显示图像,或者编写在自己的工作中使用宽度屏幕数据数据的代码

                      【讨论】:

                        【解决方案21】:

                        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;

                        【讨论】:

                        • 建议:关于如何使用此答案中的代码的一些额外解释将使其更有价值。
                        猜你喜欢
                        • 2023-03-20
                        • 2012-06-13
                        • 2013-09-07
                        • 1970-01-01
                        • 2015-02-03
                        • 2013-08-11
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多