我有一个使用React组件的复杂网页,我正在尝试将页面从静态布局转换为响应更快,可调整大小的布局。但是,我一直遇到React的限制,我想知道是否有处理这些问题的标准模式。在我的特定情况下,我有一个组件,它呈现为带有display的div:table-cell和width:auto。
不幸的是,我无法查询组件的宽度,因为你不能计算元素的大小,除非它实际放在DOM中(它具有完整的上下文来推断实际的元素)渲染宽度)。除了将它用于相对鼠标定位之类的东西之外,我还需要这个来正确设置组件中SVG元素的宽度属性。
此外,当窗口调整大小时,如何在安装过程中将大小更改从一个组件传递到另一个组件?我们在shouldComponentUpdate中执行了所有第三方SVG渲染,但是您无法在自己或该方法中的其他子组件上设置状态或属性。
是否有使用React处理此问题的标准方法?
答案 0 :(得分:64)
最实用的解决方案是使用react-measure。
注意:由于API已更改,此代码无法与react-measure@^2.0.0
一起使用。请访问上面的链接以查看新API。
import Measure from 'react-measure'
const MeasuredComp = () => (
<Measure>
{({width}) => <div>My width is {width}</div>}
</Measure>
)
要在组件之间传递大小更改,您可以传递onMeasure
回调并存储它在某处接收的值(这些天共享状态的标准方式是使用Redux):
import Measure from 'react-measure'
import connect from 'react-redux'
import {setMyCompWidth} from './actions' // some action that stores width in somewhere in redux state
function select(state) {
return {
currentWidth: ... // get width from somewhere in the state
}
}
const MyComp = connect(select)(({dispatch, currentWidth}) => (
<Measure onMeasure={({width}) => dispatch(setMyCompWidth(width))}>
<div>MyComp width is {currentWidth}</div>
</Measure>
))
如果你真的愿意,如何自己动手:
创建一个包装器组件,用于处理从DOM获取值并监听窗口大小调整事件(或react-measure
使用的组件调整大小检测)。你告诉它从DOM获取哪些道具并提供一个渲染功能,将这些道具作为一个孩子。
在可以读取DOM道具之前,必须先渲染所渲染的内容;当这些道具在初始渲染期间不可用时,您可能希望使用style={{visibility: 'hidden'}}
,以便用户在获得JS计算布局之前无法看到它。
// @flow
import React, {Component} from 'react';
import shallowEqual from 'shallowequal';
import throttle from 'lodash.throttle';
type DefaultProps = {
component: ReactClass<any>,
};
type Props = {
domProps?: Array<string>,
computedStyleProps?: Array<string>,
children: (state: State) => ?React.Element<any>,
component: ReactClass<any>,
};
type State = {
remeasure: () => void,
computedStyle?: Object,
[domProp: string]: any,
};
export default class Responsive extends Component<DefaultProps,Props,State> {
static defaultProps = {
component: 'div',
};
remeasure: () => void = throttle(() => {
const {root} = this;
if (!root) return;
const {domProps, computedStyleProps} = this.props;
const nextState: $Shape<State> = {};
if (domProps) domProps.forEach(prop => nextState[prop] = root[prop]);
if (computedStyleProps) {
nextState.computedStyle = {};
const computedStyle = getComputedStyle(root);
computedStyleProps.forEach(prop =>
nextState.computedStyle[prop] = computedStyle[prop]
);
}
this.setState(nextState);
}, 500);
// put remeasure in state just so that it gets passed to child
// function along with computedStyle and domProps
state: State = {remeasure: this.remeasure};
root: ?Object;
componentDidMount() {
this.remeasure();
this.remeasure.flush();
window.addEventListener('resize', this.remeasure);
}
componentWillReceiveProps(nextProps: Props) {
if (!shallowEqual(this.props.domProps, nextProps.domProps) ||
!shallowEqual(this.props.computedStyleProps, nextProps.computedStyleProps)) {
this.remeasure();
}
}
componentWillUnmount() {
this.remeasure.cancel();
window.removeEventListener('resize', this.remeasure);
}
render(): ?React.Element<any> {
const {props: {children, component: Comp}, state} = this;
return <Comp ref={c => this.root = c} children={children(state)}/>;
}
}
有了这个,响应宽度变化非常简单:
function renderColumns(numColumns: number): React.Element<any> {
...
}
const responsiveView = (
<Responsive domProps={['offsetWidth']}>
{({offsetWidth}: {offsetWidth: number}): ?React.Element<any> => {
if (!offsetWidth) return null;
const numColumns = Math.max(1, Math.floor(offsetWidth / 200));
return renderColumns(numColumns);
}}
</Responsive>
);
答案 1 :(得分:43)
我认为您正在寻找的生命周期方法是componentDidMount
。这些元素已经放置在DOM中,您可以从组件refs
获取有关它们的信息。
例如:
var Container = React.createComponent({
componentDidMount: function () {
// if using React < 0.14, use this.refs.svg.getDOMNode().offsetWidth
var width = this.refs.svg.offsetWidth;
},
render: function () {
<svg ref="svg" />
}
});
答案 2 :(得分:22)
除了couchand解决方案,您还可以使用findDOMNode
var Container = React.createComponent({
componentDidMount: function () {
var width = React.findDOMNode(this).offsetWidth;
},
render: function () {
<svg />
}
});
答案 3 :(得分:5)
您可以使用我编写的I库来监视组件的渲染大小并将其传递给您。
例如:
import SizeMe from 'react-sizeme';
class MySVG extends Component {
render() {
// A size prop is passed into your component by my library.
const { width, height } = this.props.size;
return (
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
);
}
}
// Wrap your component export with my library.
export default SizeMe()(MySVG);
演示:https://react-sizeme-example-esbefmsitg.now.sh/
Github:https://github.com/ctrlplusb/react-sizeme
它使用了一种优化的基于滚动/对象的算法,这种算法是我从比我更聪明的人那里借来的。 :)