【问题标题】:How can I respond to the width of an auto-sized DOM element in React?如何响应 React 中自动调整大小的 DOM 元素的宽度?
【发布时间】:2014-10-11 21:00:30
【问题描述】:

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

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

此外,当窗口调整大小时,我如何在设置过程中将大小变化从一个组件传递到另一个组件?我们在 shouldComponentUpdate 中进行所有 3rd 方 SVG 渲染,但您不能在该方法中设置您自己或其他子组件的状态或属性。

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

【问题讨论】:

  • 顺便问一下,你确定shouldComponentUpdate 是渲染SVG 的最佳位置吗?听起来你想要的是componentWillReceivePropscomponentWillUpdate,如果不是render
  • 这可能是也可能不是您正在寻找的东西,但是有一个非常好的库:github.com/bvaughn/react-virtualized 看看 AutoSizer 组件。它会自动管理宽度和/或高度,因此您不必这样做。
  • @Maggie 也可以查看github.com/souporserious/react-measure,它是用于此目的的独立库,不会将其他未使用的东西放入您的客户端包中。
  • 嘿,我回答了一个类似的问题 here 这在某种程度上是一种不同的方法,它让您根据屏幕类型(移动设备、平板电脑、台式机)来决定渲染什么
  • @Maggie 我对此可能是错误的,但我认为 Auto Sizer 总是尝试填充其父级,而不是检测子级为适应其内容而采用的大小。两者在略有不同的情况下都有用

标签: javascript reactjs responsive


【解决方案1】:

最实用的解决方案是为此使用库,例如​​ 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 中获取哪些 props,并提供一个将这些 props 当作子项的渲染函数。

在读取 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。我还不确定处理这种情况的最佳方法是什么。
  • 很好的解释,感谢您如此透彻:) re: SSR,这里讨论了isMounted()facebook.github.io/react/blog/2015/12/16/…
  • @memeLab 我刚刚为一个不错的包装器组件添加了代码,该组件使大部分样板文件不再响应 DOM 更改,看看 :)
  • @Philll_t 是的,如果 DOM 让这件事变得更容易,那就太好了。但请相信我,使用这个库会为您省去麻烦,即使它不是获取测量值的最基本方法。
  • @Philll_t 库负责的另一件事是使用 ResizeObserver(或 polyfill)立即更新代码的大小。
【解决方案2】:

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

});

【讨论】:

  • 请注意 offsetWidth 目前在 Firefox 中不存在。
  • @ChristopherChiche 我不相信这是真的。你运行的是什么版本?它至少对我有用,而且 MDN 文档似乎暗示可以假设:developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/…
  • 好吧,我会的,这很不方便。在任何情况下,我的示例都可能是一个糟糕的示例,因为无论如何您都必须明确调整 svg 元素的大小。 AFAIK 对于任何你想要寻找的动态大小的东西都可以依赖offsetWidth
  • 对于使用 React 0.14 或更高版本的任何人,不再需要 .getDOMNode():facebook.github.io/react/blog/2015/10/07/…
  • 虽然这种方法可能感觉是访问元素最简单(并且最像 jQuery)的方法,但 Facebook 现在表示“我们建议不要这样做,因为字符串引用存在一些问题,被认为是遗留问题,并且很可能将在未来版本之一中删除。如果您当前正在使用 this.refs.textInput 访问 refs,我们建议改为使用回调模式“。您应该使用回调函数而不是字符串 ref。 Info Here
【解决方案3】:

您可以使用 findDOMNode 替代 couchand 解决方案

var Container = React.createComponent({

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

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

【讨论】:

  • 澄清一下:在 React = 0.13.x 中使用 React.findDOMNode()
  • @pxwise Aaaaa 现在对于 DOM 元素引用,您甚至不必在 React 0.14 中使用任何一个函数 :)
  • @pxwise 我相信现在是ReactDOM.findDOMNode()
  • @MichaelScheper 是的,我的代码中有一些 ES7。在我更新的答案中,react-measure 演示(我认为)是纯 ES6。当然,这很难开始……在过去的一年半里,我经历了同样的疯狂:)
  • @MichaelScheper 顺便说一句,您可能会在这里找到一些有用的指导:github.com/gaearon/react-makes-you-sad
【解决方案4】:

您可以使用我编写的 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”。但现在我要试试这个。
  • 我将不胜感激任何反馈,无论是正面的还是负面的 :-)
  • 谢谢你。 要求您设置明确的宽度和高度,所以这非常有用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-03
  • 2013-12-04
  • 1970-01-01
  • 1970-01-01
  • 2012-07-21
  • 1970-01-01
相关资源
最近更新 更多