Reactjs – 重新调整浏览器大小

当浏览器窗口resize时,如何让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') ) 

然后我有块组件(相当于上面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> ); } }); 

和块的列表/集合:

 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的窗口resize,如果是的话?

 $( window ).resize(function() { // re-render the component }); 

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

你可以监听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); } }); 

@BenAlpert是正确的,+1,我只是想提供一个他的解决scheme的修改版本, 没有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); } }); 

非常简单的解决scheme:

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

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

 class CreateContact extends Component{ state = { } constructor(props) { super(props); this.state = { windowHeight: window.innerHeight, windowWidth: window.innerWidth }; } handleResize(e) { this.setState({ windowHeight: window.innerHeight, windowWidth: window.innerWidth }); } componentDidMount() { window.addEventListener('resize', ::this.handleResize) } componentWillUnmount() { window.removeEventListener('resize', ::this.handleResize) } render() { return ( <span> {this.state.windowWidth} x {this.state.windowHeight} </span> ); } } 

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

如果你不关心副作用libs,你可以简单地使用像Packery的东西

如果使用Flux,则可以创build一个包含窗口属性的存储,以便保持纯渲染function,而不必每次都查询窗口对象。

在其他情况下,如果您希望构build响应式网站,但是您更愿意使用React内联样式进行媒体查询,或者希望HTML / JS行为根据窗口宽度进行更改,请继续阅读:

什么是反应环境,为什么我谈论它

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

React上下文对传递给你的整个应用程序来说是非常有用的东西,它永远不会改变(它被许多Flux框架通过mixin使用)。 您可以使用它来存储应用程序业务不variables(如连接的userId,以便随处可用)。

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

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

什么放在上下文中

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

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

当前的用户语言

它不会经常改变,当它完成时,整个应用程序翻译完毕后,我们必须重新渲染所有内容:一个非常好用的热键语言改变

窗口属性

宽度和高度不经常改变,但是当我们做我们的布局和行为时可能要适应。 对于布局有时可以很容易地使用CSS mediaqueries进行定制,但是有时候并不需要,并且需要不同的HTML结构。 对于你必须用Javascript处理这个行为。

您不想在每个resize事件上重新渲染所有内容,因此您必须清除resize事件。

我对你的问题的理解是,你想知道根据屏幕宽度显示多less项目。 所以你首先要定义响应的断点 ,并枚举你可以拥有的不同布局types的数量。

例如:

  • 布局“1col”,宽度<= 600
  • 布局“2col”,对于600 <宽度<1000
  • 布局“3col”,1000 <=宽度

在resize事件(消除抖动)时,可以通过查询窗口对象轻松获取当前的布局types。

然后,您可以将布局types与以前的布局types进行比较,如果已更改,则使用新的上下文重新呈现应用程序:当用户触发resize事件时,可以避免重新呈现应用程序,但实际上布局types没有改变,所以你只需要时重新渲染。

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

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

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

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

否则,你会得到警告:

警告:forceUpdate(…):只能更新已安装或已安装的组件。 这通常意味着您在卸载的组件上调用了forceUpdate()。 这是一个没有操作。 请检查XXX组件的代码。

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

不知道这是否是最好的方法,但是对我而言,首先创build一个商店,我称之为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()); } })); 

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

谢谢大家的答案。 这是我的React + Recompose。 这是一个高阶函数,它包含组件的windowHeight和windowWidth属性。

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

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

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

这可能不会帮助OP,但在我的情况下,我只需要更新我的canvas上的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; `