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

Posted

技术标签:

【中文标题】如何响应 React 中自动调整大小的 DOM 元素的宽度?【英文标题】:How can I respond to the width of an auto-sized DOM element in React? 【发布时间】: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 总是尝试填充其父级,而不是检测子级为适应其内容而采用的大小。两者在略有不同的情况下都有用 【参考方案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>
);

【讨论】:

关于这种方法的一个问题我还没有调查过,它是否会干扰 s-s-r。我还不确定处理这种情况的最佳方法是什么。 很好的解释,感谢您如此透彻:) re: s-s-r,这里讨论了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  >
        <circle cx="50" cy="50" r="40" stroke="green" stroke- 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”。但现在我要试试这个。 我将不胜感激任何反馈,无论是正面的还是负面的 :-) 谢谢你。 要求您设置明确的宽度和高度,所以这非常有用

以上是关于如何响应 React 中自动调整大小的 DOM 元素的宽度?的主要内容,如果未能解决你的问题,请参考以下文章

如何自动调整标签大小以适应固定宽度的容器?

MFC如何使控件大小随着对话框大小自动调整

如何从 DOM 中获取水平尺寸?

根据内容可用性调整 UITableViewCell 的大小作为响应

如何使用 jquery 触发一般 DOM 对象调整大小的事件

我们如何在 React Native for iOS 中调整 RCTRootView 的大小/开始?