dig*_*ake 275 javascript resize reactjs
在调整浏览器窗口大小时,如何让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')
)
Run Code Online (Sandbox Code Playgroud)
然后我有了Block组件(相当于Pin上面Pinterest示例中的一个):
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>
);
}
});
Run Code Online (Sandbox Code Playgroud)
和列表/集合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>
);
}
});
Run Code Online (Sandbox Code Playgroud)
我应该添加jQuery的窗口调整大小吗?如果是的话,在哪里?
$( window ).resize(function() {
// re-render the component
});
Run Code Online (Sandbox Code Playgroud)
有没有更"反应"的方式这样做?
Sop*_*ert 438
你可以在componentDidMount中监听,就像这个只显示窗口尺寸的组件一样(如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>;
}
Run Code Online (Sandbox Code Playgroud)
And*_*ena 128
@SophieAlpert是对的,+ 1,我只是想提供她的解决方案的修改版本,没有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);
}
});
Run Code Online (Sandbox Code Playgroud)
sen*_*tor 48
非常简单的解决方案:
resize = () => this.forceUpdate()
componentDidMount() {
window.addEventListener('resize', this.resize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize)
}
Run Code Online (Sandbox Code Playgroud)
voi*_*ice 34
这是使用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>
);
}
}
Run Code Online (Sandbox Code Playgroud)
Lea*_*per 19
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);
};
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它。
function YourComponent() {
const [width, height] = useWindowDimension();
return <>Window width: {width}, Window height: {height}</>;
}
Run Code Online (Sandbox Code Playgroud)
Ale*_*lex 17
从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}</>
}
Run Code Online (Sandbox Code Playgroud)
Seb*_*ber 13
编辑2018:现在React对上下文有一流的支持
我将尝试给出一个通用答案,针对这个特定问题,但也是一个更普遍的问题.
如果你不关心副作用库,你可以简单地使用像Packery这样的东西
如果使用Flux,则可以创建包含窗口属性的存储,以便保留纯渲染功能,而无需每次都查询窗口对象.
在其他情况下,如果您想构建一个响应式网站,但您更喜欢React内联样式到媒体查询,或者希望HTML/JS行为根据窗口宽度进行更改,请继续阅读:
什么是React上下文以及我为何谈论它
React context an不在公共API中,并允许将属性传递给整个组件层次结构.
React上下文对于传递给你的整个应用程序特别有用,这些东西永远不会改变(许多Flux框架通过mixin使用它).您可以使用它来存储应用商业不变量(例如连接的userId,以便它随处可用).
但它也可以用来存储可以改变的东西.问题是当上下文发生变化时,应该重新呈现使用它的所有组件,并且这样做并不容易,最好的解决方案通常是使用新上下文卸载/重新安装整个应用程序.记住forceUpdate不是递归的.
正如您所理解的,上下文是实用的,但是当它发生变化时会对性能产生影响,因此它不应该经常更改.
什么放在上下文中
以下是不经常更改的内容:
目前的用户语言:
它不会经常发生变化,当它发生变化时,整个应用程序都会被翻译,我们必须重新渲染所有内容:热门变换的一个非常好的用法
窗口属性
宽度和高度不经常改变,但是当我们做布局和行为时可能需要适应.对于布局,有时可以很容易地使用CSS媒体查询进行自定义,但有时它不是并且需要不同的HTML结构.对于您必须使用Javascript处理此问题的行为.
您不希望在每个resize事件上重新渲染所有内容,因此您必须去抖动调整大小事件.
我对您的问题的理解是,您想知道根据屏幕宽度显示多少项目.因此,您首先要定义响应断点,并枚举您可以拥有的不同布局类型的数量.
例如:
在调整大小事件(去抖动)时,您可以通过查询窗口对象轻松获取当前布局类型.
然后,您可以将布局类型与以前的布局类型进行比较,如果已更改,则使用新的上下文重新呈现应用程序:这样可以避免在用户触发调整大小事件时实际重新呈现应用程序布局类型未更改,因此您只需在需要时重新呈现.
一旦你有了,你可以简单地在你的应用程序中使用布局类型(可以通过上下文访问),这样你就可以自定义HTML,行为,CSS类......你知道你的布局类型在React渲染函数中,所以这意味着你可以使用内联样式安全地编写响应式网站,根本不需要媒体查询.
如果您使用Flux,您可以使用商店而不是React上下文,但如果您的应用程序有很多响应组件,那么使用上下文可能更简单吗?
gkr*_*kri 10
我使用@senornestor的解决方案,但要完全正确,你还必须删除事件监听器:
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount(){
window.removeEventListener('resize', this.handleResize);
}
handleResize = () => {
this.forceUpdate();
};
Run Code Online (Sandbox Code Playgroud)
否则你会收到警告:
警告:forceUpdate(...):只能更新已安装或安装的组件.这通常意味着您在未安装的组件上调用了forceUpdate().这是一个无操作.请检查XXX组件的代码.
我将跳过所有上述答案并开始使用react-dimensions高阶组件.
https://github.com/digidem/react-dimensions
只需添加一个简单的import和函数调用,您可以访问this.props.containerWidth,并this.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
Run Code Online (Sandbox Code Playgroud)
此代码使用新的React上下文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>;
}
}
Run Code Online (Sandbox Code Playgroud)
然后你可以在代码中的任何地方使用它,如下所示:
<WindowConsumer>
{({ width, height }) => //do what you want}
</WindowConsumer>
Run Code Online (Sandbox Code Playgroud)
您不一定需要强制重新渲染.
这可能没有帮助OP,但在我的情况下我只需要更新我的画布上的width和height属性(你不能用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;
`
Run Code Online (Sandbox Code Playgroud)
这个厨房里有很多厨师,但无论如何我都会投身其中。requestAnimationFrame我认为这些用途都不是性能最好的。
这是一个使用 React hooks 和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;
}
Run Code Online (Sandbox Code Playgroud)
这是显示其使用情况的要点:Img.tsx with useResize。或者,您可以在我的存储库中查看它以获取更多上下文。
关于为什么应该这样做而不是消除函数反跳的一些资源:
感谢您参加我的 Ted 演讲。
不确定这是否是最好的方法,但对我来说最有效的是创建一个商店,我称之为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;
Run Code Online (Sandbox Code Playgroud)
然后我在我的组件中使用了那个商店,有点像这样:
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());
}
}));
Run Code Online (Sandbox Code Playgroud)
仅供参考,这些文件被部分修剪.
I know this has been answered but just thought I'd share my solution as the top answer, although great, may now be a little outdated.
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 })
}
Run Code Online (Sandbox Code Playgroud)
The only difference really is that I'm debouncing (only running every 200ms) the updateWindowDimensions on the resize event to increase performance a bit, BUT not debouncing it when it's called on ComponentDidMount.
I was finding the debounce made it quite laggy to mount sometimes if you have a situation where it's mounting often.
Just a minor optimisation but hope it helps someone!
想分享我刚刚发现使用的这个非常酷的东西 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
}
};
Run Code Online (Sandbox Code Playgroud)
.matches 如果窗口高于或低于指定的 max-width 值,将返回 true 或 false,这意味着不需要限制侦听器,因为 matchMedia 仅在布尔值更改时触发一次。
我的代码可以很容易地调整为包含useState保存布尔 matchMedia 返回,并使用它来有条件地呈现组件、触发动作等。
| 归档时间: |
|
| 查看次数: |
201575 次 |
| 最近记录: |