首页 文章

如何在React中响应自动调整大小的DOM元素的宽度?

提问于
浏览
82

我有一个使用React组件的复杂网页,我试图将页面从静态布局转换为响应更快,可调整大小的布局 . 但是,我一直遇到React的限制,我想知道是否有处理这些问题的标准模式 . 在我的特定情况下,我有一个组件呈现为带有display的div:table-cell和width:auto .

不幸的是,我无法查询我的组件的宽度,因为你无法计算元素的大小,除非它实际放在DOM中(它具有用于推导实际渲染宽度的完整上下文) . 除了将它用于相对鼠标定位之类的东西之外,我还需要这个来正确设置组件内SVG元素的宽度属性 .

此外,当窗口调整大小时,如何在安装过程中将大小更改从一个组件传递到另一个组件?我们在shouldComponentUpdate中执行所有第三方SVG渲染,但您无法在自己或该方法中的其他子组件上设置状态或属性 .

有没有一种使用React处理这个问题的标准方法?

4 回答

  • 64

    最实用的解决方案是使用react-measure .

    Note :由于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>
    ))
    

    How to roll your own if you really prefer to:

    创建一个包装器组件,用于处理从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>
    );
    
  • 22

    我认为您正在寻找的生命周期方法是 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" />
      }
    
    });
    
  • 5

    除了couchand解决方案,您还可以使用findDOMNode

    var Container = React.createComponent({
    
      componentDidMount: function () {
        var width = React.findDOMNode(this).offsetWidth;
      },
    
      render: function () {
        <svg />
      }
    });
    
  • 42

    您可以使用我编写的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

    它使用了一种优化的基于滚动/对象的算法,这种算法是我从比我更聪明的人那里借来的 . :)

相关问题