当浏览器窗口调整大小时,如何让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”的方式来做到这一点?
这个厨房里有很多厨师,但不管怎样我都要把我的帽子扔进去。这些都没有使用requestAnimationFrame,我认为这是性能最好的。
下面是一个使用React钩子和requestAnimationFrame的例子。这也使用纯js,没有任何库,如lodash(由于捆绑包的大小,我尽量避免使用)。
import { useState, useEffect, useCallback } from 'react';
const getSize = () => {
return {
width: window.innerWidth,
height: window.innerHeight,
};
};
export function useResize() {
const [size, setSize] = useState(getSize());
const handleResize = useCallback(() => {
let ticking = false;
if (!ticking) {
window.requestAnimationFrame(() => {
setSize(getSize());
ticking = false;
});
ticking = true;
}
}, []);
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
下面是它使用的要点:Img。tsx和useResize。或者,你可以在我的回购中看到更多的上下文。
一些关于为什么你应该这样做而不是debouning你的函数的参考资料:
一个相关的github公关与良好的解释
中等,但在一个迷因墙后面
一个深入的stackoverflow回答
谢谢你们来听我的Ted演讲。
这个厨房里有很多厨师,但不管怎样我都要把我的帽子扔进去。这些都没有使用requestAnimationFrame,我认为这是性能最好的。
下面是一个使用React钩子和requestAnimationFrame的例子。这也使用纯js,没有任何库,如lodash(由于捆绑包的大小,我尽量避免使用)。
import { useState, useEffect, useCallback } from 'react';
const getSize = () => {
return {
width: window.innerWidth,
height: window.innerHeight,
};
};
export function useResize() {
const [size, setSize] = useState(getSize());
const handleResize = useCallback(() => {
let ticking = false;
if (!ticking) {
window.requestAnimationFrame(() => {
setSize(getSize());
ticking = false;
});
ticking = true;
}
}, []);
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
下面是它使用的要点:Img。tsx和useResize。或者,你可以在我的回购中看到更多的上下文。
一些关于为什么你应该这样做而不是debouning你的函数的参考资料:
一个相关的github公关与良好的解释
中等,但在一个迷因墙后面
一个深入的stackoverflow回答
谢谢你们来听我的Ted演讲。
使用React钩子:
你可以定义一个自定义钩子来监听窗口大小调整事件,就像这样:
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>窗口大小: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);
}
}
不确定这是否是最好的方法,但对我来说有效的方法是首先创建一个商店,我称之为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());
}
}));
供你参考,这些文件被部分删减了。
这是一个不使用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>
);
};