我有一个使用React组件的复杂网页,并且正在尝试将该页面从静态布局转换为响应更快,可调整大小的布局。但是,我一直遇到React的限制,并且想知道是否存在用于处理这些问题的标准模式。在我的特定情况下,我有一个使用display:table- cell和width:auto呈现为div的组件。
不幸的是,我无法查询组件的宽度,因为您无法计算元素的大小,除非将其实际放置在DOM中(该元素具有推论实际渲染宽度的完整上下文)。除了用于相对鼠标定位之类的功能外,我还需要此功能来在组件内的SVG元素上正确设置宽度属性。
另外,当窗口调整大小时,如何在安装过程中将尺寸变化从一个组件传递到另一个组件?我们正在shouldComponentUpdate中完成所有第三方SVG渲染,但是您无法在该方法中设置自己或其他子组件的状态或属性。
是否有使用React处理此问题的标准方法?
最实用的解决方案是使用库来进行类似react-measure的操作。
更新 :现在有一个用于调整大小检测的自定义钩子(我没有亲自尝试过):react-resize- aware。作为自定义挂钩,它看起来比起来更方便react-measure。
react-measure
import * as React from 'react' import Measure from 'react-measure' const MeasuredComp = () => ( <Measure bounds> {({ measureRef, contentRect: { bounds: { width }} }) => ( <div ref={measureRef}>My width is {width}</div> )} </Measure> )
为了传达组件之间的大小变化,您可以传递一个onResize回调并将接收到的值存储在某个地方(如今,共享状态的标准方法是使用Redux):
onResize
import * as React from 'react' import Measure from 'react-measure' import { useSelector, useDispatch } from 'react-redux' import { setMyCompWidth } from './actions' // some action that stores width in somewhere in redux state export default function MyComp(props) { const width = useSelector(state => state.myCompWidth) const dispatch = useDispatch() const handleResize = React.useCallback( (({ contentRect })) => dispatch(setMyCompWidth(contentRect.bounds.width)), [dispatch] ) return ( <Measure bounds onResize={handleResize}> {({ measureRef }) => ( <div ref={measureRef}>MyComp width is {width}</div> )} </Measure> ) }
如果您确实喜欢以下内容,该如何滚动自己:
创建一个包装器组件,该组件处理从DOM获取值并监听窗口调整大小事件(或所使用的组件调整大小检测react- measure)。您告诉它要从DOM获取哪些道具,并提供将这些道具作为子对象的渲染函数。
react- measure
必须先安装渲染的内容,然后才能读取DOM属性;当这些道具在初始渲染期间不可用时,您可能需要使用它,style={{visibility: 'hidden'}}以便用户在获得JS计算的布局之前看不到它。
style={{visibility: 'hidden'}}
// @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> );