简体   繁体   English

如何从父组件中的子元素获取累积高度

[英]How to get the cumulated height from children elements within a parent component

really sorry about that weirdly formulated question. 对于这个怪异的问题,我深表歉意。 I am looking to create aa short spring animation with react-motion. 我正在寻找用react-motion创建一个短弹簧动画。 Please bear with me: 请多多包涵:

I have a component that loads a list of local directories and files inside a frame, a bit like a mac finder window, according to a selected base directory. 我有一个组件,可以根据选定的基本目录在框架内加载本地目录和文件列表,有点像mac finder窗口。 So, depending on that arbitrary directory, the number of fetched files will vary and so the height of my frame will do too (until it reaches max-height). 因此,取决于该任意目录,提取文件的数量将有所不同,因此帧的高度也将发生变化(直到达到最大高度)。

Now, as soon as the content of the selected directory has been fetched, I want to spring open this component's frame dynamically to the right height (or max-height). 现在,一旦获取了所选目录的内容,我想将该组件的框架动态弹开到正确的高度(或max-height)。 And if I was to change the directory, I'd need the frame height to spring/adjust according to the number of elements fetched OR straight to the max-height. 如果要更改目录,则需要根据获取的元素数或直接将其调整为最大高度来调整/调整框架的高度。

Here is the relevant excerpt of the component: 这是组件的相关摘录:

  <Motion
    defaultStyle={{x: 30}}
    style={{x: spring(330, {stiffness: 270, damping: 17})}}
  >
    {({x}) =>  
      <List height={x}>
        {
          localTree.map(item =>
            <ListItem key={v4()}>
              <FileIcon isDirectory={item.isDirectory} />
              <span onClick={() => this.handleClick(item.id)}>
                {item.baseName}
              </span>
            </ListItem>
          )
        }
      </List>
    }
  </Motion>

You can see that, at the moment, the defaultStyle and style variables are statically set. 您可以看到,目前, defaultStylestyle变量是静态设置的。 It works fine, especially because I've set the <List /> component's style to overflow: scroll so that if there are more elements than 330px in this case, a little scrolling makes it naturally browsable. 效果很好,特别是因为我已经将<List />组件的样式设置为overflow: scroll以便在这种情况下,如果元素的330px超过330px ,则稍微滚动一下就可以自然浏览。

But that is not what I ultimately want. 但这不是我最终想要的。 I want the defaultStyle={{x: 30}} and style={{x: spring(330, )}} to be dynamically set by the height of the cumulated number of <ListItem /> child elements. 我希望通过<ListItem />子元素的累计数量的高度动态设置defaultStyle={{x: 30}}style={{x: spring(330, )}}

How should I tackle this? 我该如何解决? Should I add a parent component somewhere? 我应该在某个地方添加父组件吗?

This is an extremely interesting question, very chicken and egg, how can you measure the height of something that needs to be rendered in order to measure it, without the user seeing it flash up first. 这是一个非常有趣的问题,非常鸡和蛋,如何测量需要渲染才能测量的物体的高度,而又不会让用户首先看到它的闪烁。 Poor solutions to this may involve rendering something off screen etc and measuring it, but that doesn't sit well with the concepts React promotes. 较差的解决方案可能涉及在屏幕上渲染某些东西并对其进行测量,但这与React提出的概念并不吻合。

It can however be solved by understanding not only the component lifecycle, but how that fits in respect to the browser lifecycle. 但是,不仅可以通过了解组件生命周期,而且可以了解其与浏览器生命周期的关系来解决该问题。

Key to this are componentDidMount and componentDidUpdate . 关键是componentDidMountcomponentDidUpdate A common misconception is that these occur after the component has been rendered to the browser, not true, it fires just before that happens 一个常见的误解是,这些错误发生在将组件呈现给浏览器之后,而不是真实的,它在此之前就触发了

render will first reconcile the virtual DOM with anything you have asked to render (another misconception here - the virtual DOM is not actually a real or even shadow DOM, it is just a tree of JS objects representating the components and their DOM that the algorithm can derive diffs from - you cannot ascertain dynamic heights or interact with real DOM elements at this point). render首先会将虚拟DOM与您要求渲染的任何内容进行调和(这里的另一个误解-虚拟DOM实际上不是真正的甚至是影子DOM,它只是一棵JS对象树,代表了算法可以实现的组件及其DOM派生差异-您目前无法确定动态高度或与实际DOM元素进行交互)。

If it senses that there are actual DOM mutations to be done then componentDidMount / componentDidUpdate now fires BEFORE actual browser rendering. 如果感觉到有实际的DOM变异需要执行,那么componentDidMount / componentDidUpdate现在会在实际的浏览器渲染之前触发。

It will then perform the derived updates to the real DOM (effectively rendering it to the browser DOM). 然后,它将执行对真实DOM的派生更新(有效地renderingrendering给浏览器DOM)。 Crucially though the browser lifecycle means that this rendered DOM does not yet get painted to the screen (ie it is not rendered from the user's perspective), until the next tick when requestAnimationFrame has had a chance to intercept it... 至关重要的是,尽管浏览器生命周期意味着此渲染的DOM尚未被绘制到屏幕上(即,从用户的角度来看,它尚未rendered ),直到requestAnimationFrame有机会拦截它的下一个刻度为止。

componentWillReceiveProps
 |
 V
shouldComponentUpdate
 |
 V
componentWillUpdate
 |
 V
render
 |
 V
componentDidUpdate
 |
 V
[BROWSER DOM rendered - can be interacted with programatically]
 |
 V
requestAnimationFrame callback
 |
 V
[BROWSER screen painted with updated DOM - can be seen and interacted by user]

So at this point in componentDidX we have the opportunity to tell it to intercept the browser BEFORE it paints and yet we can interact with the real updated DOM and perform measurements! 因此,在这一点上,我们可以在componentDidX中告诉它在绘制之前拦截浏览器,但是我们可以与真正的更新DOM交互并执行测量! To do this we need to use window.requestAnimationFrame which takes a callback function in which we can perform a measurement. 为此,我们需要使用window.requestAnimationFrame ,它带有一个回调函数,可以在其中执行测量。 As this is an async function it returns an id that you can use to cancel it much like setTimeout does (and we should remember to clean up any pending requests so from componentWillUnmount ). 由于这是一个异步函数,因此它返回一个ID,您可以像setTimeout一样使用它来取消它(并且我们应该记住要从componentWillUnmount清除所有未决请求)。

We will also need a measurable parent element that does not get styles set on it, with a ref stored so we can get at the DOM node to measure it. 我们还将需要一个没有设置样式的可测量父元素,并存储一个ref以便我们可以在DOM节点处对其进行度量。 And a wrapper that will be animated in height. 还有一个将高度动画化的包装器。

Simplified without react-motion (but the outer div can be wrapped in a Motion and can plug the interpolation in to the dynamic style prop)... 无需反作用力即可简化(但外部div可以包装在Motion并且可以将插值插入动态样式道具中)...

  // added type info for better clarity

  constructor(props: SomeProps) {
    super(props)
    this.state = {
      height: undefined
    }
  }

  listRef: HTMLElement

  bindListRef = (el: HTMLElement): void => {
    this.listRef = el
  }

  rafId: number

  interceptAndMeasure = (): void {
    // use request animation frame to intercept and measure new DOM before the user sees it
    this.rafId = window.requestAnimationFrame(this.onRaf)
  }

  onRaf = (): void => {
    const height: number = this.listRef.getBoundingClientRect().height
    // if we store this in the state - the screen will not get painted, as a new complete render lifecycle will ensue
    // BEWARE you must have a guard condition against setting state unnecessarily here
    // as it is triggered from within `componentDidX` which can if unchecked cause infinite re-render loops!
    if (height !== this.state.height) this.setState({height})
  }

  render() {
    const {data} = this.props
    const {height} = this.state.height
    // the ul will be pushed out to the height of its children, and we store a ref that we can measure it by
    // the wrapper div can have its height dynamically set to the measured value (or an interpolated interim animated value!) 
    return (
      <div style={height !== undefined ? {height: `${height}px`} : {}}>
        <ul ref={this.bindListRef}>
          {data.map((_, i) => 
            <li key={i}>
              {_.someMassiveText}
            </li>
          )}
        </ul>
      </div>
    )
  }

  componentDidMount() {
    this.interceptAndMeasure()
  }

  componentDidUpdate() {
    this.interceptAndMeasure()
  }

  componentWillUnmount() {
    // clean up in case unmounted while still pending
    window.cancelAnimationFrame(this.rafId)
  }

Of course this has been encountered before and as luck would have it you can skip having to worry about implementing it yourself and use the excellent react-collapse package ( https://github.com/nkbt/react-collapse ) which uses react-motion beneath the surface to do exactly this, and also handles many edge cases like nesting dynamic height etc 当然,这是以前遇到过的,幸运的是,您可以不必担心自己实施,而使用出色的react-collapse软件包( https://github.com/nkbt/react-collapse ),该软件包使用react-motion在表面下react-motion以精确地做到这一点,还可以处理许多边缘情况,例如嵌套动态高度等

But before you do, I really urge you to at least give this a try yourself as it opens up a much deeper understanding of both the React and browser lifecycle, and will help with deeply interactive experiences you would have a hard time achieving otherwise 但是在您这样做之前,我真的强烈建议您至少尝试一下,因为它可以使您对React和浏览器的生命周期有更深入的了解,并且将有助于您进行深度交互,否则您将很难实现

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM