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

Ste*_*sch 84 javascript reactjs responsive

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

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

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

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

And*_*ndy 64

最实用的解决方案是使用反应测量.

注意:此代码不适react-measure@^2.0.0用于API已更改.请访问上面的链接以查看新API.

import Measure from 'react-measure'

const MeasuredComp = () => (
  <Measure>
    {({width}) => <div>My width is {width}</div>}
  </Measure>
)
Run Code Online (Sandbox Code Playgroud)

要在组件之间传递大小更改,您可以传递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>
))
Run Code Online (Sandbox Code Playgroud)

如果你真的喜欢,如何自己动手:

创建一个包装器组件,用于处理从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)}/>;
  }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,响应宽度变化非常简单:

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>
);
Run Code Online (Sandbox Code Playgroud)


cou*_*and 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" />
  }

});
Run Code Online (Sandbox Code Playgroud)

  • 对于使用React 0.14或更高版本来到这里的任何人,不再需要.getDOMNode():https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#dom-节点裁判 (3认同)
  • 虽然这种方法可能感觉是访问元素的最简单(也最像 jQuery)方法,但 Facebook 现在表示“我们建议不要使用它,因为字符串引用存在一些问题,被认为是遗留的,并且可能会在未来被删除版本。如果您当前正在使用 this.refs.textInput 来访问 refs,我们建议您改用回调模式”。您应该使用回调函数而不是字符串引用。[信息在这里](https://facebook.github.io/react/docs/refs-and-the-dom.html) (2认同)

Luk*_*don 22

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

var Container = React.createComponent({

  componentDidMount: function () {
    var width = React.findDOMNode(this).offsetWidth;
  },

  render: function () {
    <svg />
  }
});
Run Code Online (Sandbox Code Playgroud)

  • 为了澄清:在React <= 0.12.x中使用component.getDOMNode(),在React> = 0.13.x中使用React.findDOMNode() (10认同)
  • @pxwise我相信它现在是`ReactDOM.findDOMNode()` (5认同)
  • @pxwise Aaaaaand现在为DOM元素引用你甚至不必使用React 0.14的任何一个函数:) (2认同)
  • @MichaelScheper btw,你可能会在这里找到一些有用的指导:https://github.com/gaearon/react-makes-you-sad (2认同)

ctr*_*usb 6

您可以使用我编写的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);   
Run Code Online (Sandbox Code Playgroud)

演示:https://react-sizeme-example-esbefmsitg.now.sh/

Github:https://github.com/ctrlplusb/react-sizeme

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