如何响应 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 的最佳位置吗?听起来你想要的是componentWillReceiveProps
或componentWillUpdate
,如果不是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 元素的宽度?的主要内容,如果未能解决你的问题,请参考以下文章
根据内容可用性调整 UITableViewCell 的大小作为响应