浏览器上的Rerender视图使用React调整大小

时间:2013-09-25 20:06:06

标签: javascript reactjs resize

如何在调整浏览器窗口大小时让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
});

是否有更“反应”的方法呢?

21 个答案:

答案 0 :(得分:370)

你可以在componentDidMount中监听,就像这个只显示窗口尺寸的组件一样(如<span>1024 x 768</span>):

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {
        this.setState({width: $(window).width(), height: $(window).height()});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});

答案 1 :(得分:114)

@SophieAlpert是对的,+ 1,我只想根据this answer提供她的解决方案的修改版本 without 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);
    }
});

答案 2 :(得分:39)

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

resize = () => this.forceUpdate()

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

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

答案 3 :(得分:30)

这是一个使用es6而不使用jQuery的简单而简短的例子。

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

答案 4 :(得分:14)

编辑2018 :现在React拥有对context

的一流支持

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

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

这样的东西

如果使用Flux,则可以创建包含窗口属性的存储,以便保留纯渲染功能,而无需每次都查询窗口对象。

在其他需要构建响应式网站的情况下,但您更喜欢将React内联样式用于媒体查询,或者希望HTML / JS行为根据窗口宽度进行更改,请继续阅读:

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

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

React上下文特别适用于传递给您的整个应用程序永不改变的东西(许多Flux框架通过mixin使用它)。您可以使用它来存储应用商业不变量(例如连接的userId,以便它随处可用)。

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

正如您所理解的那样,上下文是实用的,但是当它发生变化时会对性能产生影响,因此它不应该经常更改。

在上下文中放置什么

  • 不变量:像连接的userId,sessionToken,无论......
  • 不经常改变的事情

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

当前用户语言

它不会经常发生变化,当它发生变化时,整个应用程序都会被翻译,我们必须重新渲染所有内容:一个非常好用的热变换

窗口属性

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

您不想在每次调整大小事件中重新渲染所有内容,因此您必须去除调整大小事件。

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

例如:

  • 布局&#34; 1col&#34;,宽度&lt; = 600
  • 布局&#34; 2col&#34;,600&lt;宽度&lt; 1000
  • 布局&#34; 3col&#34;,1000&lt; = width

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

然后,您可以将布局类型与以前的布局类型进行比较,如果已更改,则使用新上下文重新呈现应用程序:这样可以避免在用户触发调整大小事件时重新呈现应用程序但实际上布局类型没有改变,因此您只需在需要时重新渲染。

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

如果您使用Flux,您可以使用商店而不是React上下文,但如果您的应用有很多响应组件,那么使用上下文可能更简单吗?

答案 5 :(得分:9)

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

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

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

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

否则你会收到警告:

  

警告:forceUpdate(...):只能更新已安装或已安装   零件。这通常意味着您在卸载时调用了forceUpdate()   零件。这是一个无操作。请检查XXX的代码   成分

答案 6 :(得分:6)

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

答案 7 :(得分:6)

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

答案 8 :(得分:6)

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

这可能对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;
`

答案 9 :(得分:5)

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

然后我在我的组件中使用了那个商店,有点像这样:

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

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

答案 10 :(得分:4)

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

    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进行了debouncing(仅运行每200ms)以稍微提高性能,但是当它在ComponentDidMount上调用时不会对它进行去抖动。

我发现,如果你有经常安装的情况,那么有时我会发现去抖使得它有时很难安装。

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

答案 11 :(得分:4)

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

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

这通常会导致性能问题,因此我编写了import tkinter as tk from tkinter import ttk win = tk.Tk() win.title("Vitek magacin") win.geometry("200x150") win.configure(background='gold') def check(): c1=no1.get() c2=no2.get() if c1 == c2: print("You win a silly price") else: print("You win nothing old men") no1=tk.StringVar() no2=tk.StringVar() inputa = ttk.Entry(win, width=12, textvariable=no1) inputa.grid(column=0, row=1) inputa.focus() inputb = ttk.Entry(win, width=12, textvariable=no2) inputb.grid(column=0, row=2) ButtonCheck = ttk.Button(win, text='Check', command=check) ButtonCheck.grid(column=0, row=3) win.mainloop() 钩子,该钩子会在短时间内消除useWindowDimension事件的反弹。例如100ms

resize

像这样使用它。

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

答案 12 :(得分:3)

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

答案 13 :(得分:2)

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

仅需要定义大小调整事件功能。

然后更新渲染器大小(canvas),为相机分配新的宽高比。

在我看来,卸载和重新安装是一种疯狂的解决方案。...

如果需要的话,下面是坐骑。

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

答案 14 :(得分:1)

谢谢大家的答案。这是我的React + 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');
 }})
)

答案 15 :(得分:1)

https://github.com/renatorib/react-sizes是一种在保持良好性能的同时实现此目的的方法。

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

答案 16 :(得分:1)

只需改进@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 :(得分:0)

必须在构造函数中将其绑定到'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)
  }
}

答案 18 :(得分:0)

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

答案 19 :(得分:0)

想分享我刚刚使用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返回值,并使用它有条件地渲染组件,触发动作等。

答案 20 :(得分:0)

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;