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


86

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

不幸的是,我无法查询组件的宽度,因为您无法计算元素的大小,除非将元素实际放置在DOM中(该元素具有推断实际呈现的宽度的完整上下文)。除了将其用于诸如相对鼠标定位之类的事情之外,我还需要它来在组件内的SVG元素上正确设置宽度属性。

另外,当窗口调整大小时,如何在设置过程中将尺寸变化从一个组件传递到另一个组件?我们正在shouldComponentUpdate中完成所有所有的第三方SVG渲染,但是您无法在该方法中设置自己或其他子组件的状态或属性。

是否有使用React处理此问题的标准方法?


1
顺便说一句,您确定shouldComponentUpdate渲染SVG的最佳位置吗?这听起来像是您想要的,componentWillReceiveProps或者componentWillUpdate不是render
安迪

1
这可能不是您正在寻找的东西,但是有一个很好的库:github.com/bvaughn/react-virtualized 看一下AutoSizer组件。它会自动管理宽度和/或高度,因此您不必这样做。
玛姬

@Maggie也请查看github.com/souporserious/react-measure,它是用于此目的的独立库,不会将其他未使用的内容放入客户端包中。
安迪

嘿,我在这里回答了类似的问题这是一种不同的方法,它使您可以根据自己的屏幕类型(移动设备,平板电脑,台式机)来决定要渲染的内容
Calin ortan

@Maggie我对此可能是错的,但我认为Auto Sizer总是尝试填充其父级,而不是检测子级已采用的大小以适合其内容。两者在略有不同的情况下都很有用
Andy

Answers:


65

最实用的解决方案是使用库来进行类似react-measure的操作

更新:现在有一个用于调整大小检测的自定义钩子(我没有亲自尝试过):react-resize-aware。作为自定义挂钩,它看起来比起来更方便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):

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获得哪些道具,并提供将这些道具作为子项的渲染功能。

必须先安装渲染的内容,然后才能读取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>
);

我尚未调查的有关此方法的一个问题是,它是否会干扰SSR。我还不确定处理这种情况的最佳方法是什么。
安迪

很好的解释,谢谢你是如此彻底:)回复:SSR,还有的讨论isMounted()在这里:facebook.github.io/react/blog/2015/12/16/...
ptim

1
@memeLab我刚刚为一个不错的包装器组件添加了代码,该组件使大多数样板都无法响应DOM更改,请看一下:)
Andy

1
@Philll_t是的,如果DOM使此过程更容易,那将很好。但是请相信我,即使这不是进行测量的最基本方法,使用该库也可以省去您的麻烦。
安迪

1
@Philll_t库需要注意的另一件事是使用ResizeObserver(或polyfill)立即获取代码的大小更新。
安迪

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" />
  }

});

1
请注意,offsetWidthFirefox当前不存在。
Christopher Chiche

@ChristopherChiche我不相信那是真的。您正在运行什么版本?它至少对我
有用

1
好吧,那会很不方便。无论如何,我的示例可能是一个糟糕的示例,因为svg无论如何您都必须显式确定元素的大小。AFAIK可以满足您寻找动态尺寸所需的任何东西offsetWidth
benchand

3
对于使用React 0.14或更高版本来这里的任何人,都不再需要.getDOMNode():facebook.github.io/react/blog/2015/10/07/…–
Hamund

2
尽管此方法似乎是最简单(和jQuery最类似)的访问元素的方法,但Facebook现在说:“我们建议不要这样做,因为字符串引用存在一些问题,被认为是遗留问题,并且很可能在将来的某个时间被删除。如果您当前正在使用this.refs.textInput访问参考,我们建议您使用回调模式”。您应该使用回调函数而不是字符串ref。这里的信息
ahaurat

22

除了沙发和解决方案,您还可以使用findDOMNode

var Container = React.createComponent({

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

  render: function () {
    <svg />
  }
});

10
澄清一下:在React <= 0.12.x中,使用component.getDOMNode(),在React> = 0.13.x中,使用React.findDOMNode()
pxwise

2
@pxwise Aaaaaand现在对于DOM元素引用,您甚至不必在React 0.14中使用任何一个函数:)
Andy

5
@pxwise我相信是ReactDOM.findDOMNode()现在?
ivarni '16

1
@MichaelScheper是的,我的代码中有一些ES7。在我最新的答案中,该react-measure演示是(我认为)纯ES6。肯定很难上手,在过去的一年半里,我经历了同样的疯狂:)
Andy

2
@MichaelScheper顺便说一句,您可能会在这里找到一些有用的指导:github.com/gaearon/react-makes-you-sad
Andy

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);   

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

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

它使用优化的基于滚动/对象的算法,我从比我聪明得多的人那里借来的。:)


很好,谢谢分享。我也将为“ DimensionProvidingHoC”的自定义解决方案创建一个存储库。但是现在我要尝试一下。
larrydahooster '16

我将不胜感激任何反馈,无论是正面还是负面的:-)
ctrlplusb

这次真是万分感谢。<Recharts />要求您设置一个明确的宽度和高度,所以这非常有帮助
JP DeVries
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.