当浏览器窗口调整大小时,如何让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”的方式来做到这一点?
只是为了改进@senornestor使用forceUpdate的解决方案和@gkri在组件卸载时删除resize事件监听器的解决方案:
不要忘记抑制(或撤消)调整大小的要求
确保在构造函数中绑定(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>
)
}
}
另一种方法是使用一个“dummy”状态来代替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>
)
}
}
对于那些寻求Typescript支持的人,我根据@Lead Developer的回答写了下面的钩子:
import { useState, useEffect } from 'react';
export const debounce = <A extends unknown[]>(callback: (...args: A) => unknown, msDelay: number) => {
let timer: NodeJS.Timeout | undefined;
return (...args: A) => {
clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
callback(...args);
}, msDelay);
};
};
export const useWindowDimension = (msDelay = 100) => {
const [dimension, setDimension] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const resizeHandler = () => {
setDimension({
width: window.innerWidth,
height: window.innerHeight
});
};
const handler = msDelay <= 0 ? resizeHandler : debounce(resizeHandler, msDelay);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return dimension;
};
export type Dimension = ReturnType<typeof useWindowDimension>;
很明显,它的好处是Typescript支持,还有一个选项可以控制延迟时间(以毫秒为单位),默认为100ms。如果延迟时间为0或更少,它将不会反弹处理程序。
另一个区别是钩子返回一个Dimension类型的对象,即:
type Dimension = {
width: number;
height: number;
}
这样,每个人都清楚钩子实际返回的是什么。