如何测量 child 的高度并将其传递给 React 中的 parent?

How to measure the height of a child and pass it to a parent in React?

我想计算组件的高度,并在页面加载和调整大小时将其发送到 parent。

我正在使用下面的可重用 Hook 成功测量 header 组件内 div 的高度。但是如何将 child 中的 useDimensions 计算出的 height 作为 headHeight 发送到其 parent 组件?

量钩

import { useState, useCallback, useEffect } from 'react';

function getDimensionObject(node) {
  const rect = node.getBoundingClientRect();

  return {
    width: rect.width,
    height: rect.height,
    top: 'x' in rect ? rect.x : rect.top,
    left: 'y' in rect ? rect.y : rect.left,
    x: 'x' in rect ? rect.x : rect.left,
    y: 'y' in rect ? rect.y : rect.top,
    right: rect.right,
    bottom: rect.bottom
  };
}

export function useDimensions(data = null, liveMeasure = true) {
  const [dimensions, setDimensions] = useState({});
  const [node, setNode] = useState(null);

  const ref = useCallback(node => {
    setNode(node);
  }, []);

  useEffect(() => {
    if (node) {
      const measure = () =>
        window.requestAnimationFrame(() =>
          setDimensions(getDimensionObject(node))
        );
      measure();

      if (liveMeasure) {
        window.addEventListener('resize', measure);
        window.addEventListener('scroll', measure);

        return () => {
          window.removeEventListener('resize', measure);
          window.removeEventListener('scroll', measure);
        };
      }
    }
  }, [node, data]);

  return [ref, dimensions, node];
}

Parent

export default function Main(props: { notifications: Notification[] }) {
    const { notifications } = props;
    const [headHeight, setHeadHeight] = useState(0)

    const updateHeadHeight = () => {
        setHeadHeight(headHeight)
    }

    return (
         <main>
            <Header updateParent={updateHeadHeight}/>
           {headHeight}
        </main>
    )
}

Child

import { useDimensions } from '../../lib/design/measure';
import React, { useState, useLayoutEffect } from 'react';

export default function DefaultHeader(props, data) {
    const [
        ref,
        { height, width, top, left, x, y, right, bottom }
      ] = useDimensions(data);
    ;

    return <>
       <div ref={ref} className="header">
          <h1>Hello!</h1>
       </div>   
    </>
}

您可以像这样在 DefaultHeader 组件中添加一个 useEffect 挂钩:

useEffect(() => props.updateParent(height), [props.updateParent, height])

只要检测到高度变量或 props.updateParent 属性的变化,这个钩子就应该 运行。只需确保在 useDimensions 挂钩之后声明此挂钩,这样它就不会抛出未定义的错误。

您将自己局限于做事的反应方式,这严重地使事情变得过于复杂。您可以简单地在子元素上使用 Event Bubbling to achieve what you want. All you need to do is dispatch a Custom Event 事件,该事件将通过每个祖先冒泡到 window。然后在父元素中拦截这个事件,并响应

let m = document.querySelector('main');
let s = document.querySelector('section');
let b = document.querySelector('button');

b.addEventListener('click', event => {
  s.style.height = '200px';
});

m.addEventListener('resize', event => {
  console.log('new size:', event.detail);
});

new ResizeObserver(entries => {
  for (let e of entries) {
    s.dispatchEvent(
      new CustomEvent('resize', {
        detail: e.contentRect,
        bubbles: true
      })
    );
  }
}).observe(s);
main { background: green; padding: 1rem; margin-top: 1rem; }
section { background: orange; }
<button> Test </button>

<main>
  <section></section>
</main>

只需在子组件中实现 ResizeObserver 块,在父组件中实现 resize 事件监听器。

该方法以解耦和standards-compliant的方式达到了预期的效果。子元素只是宣布对其状态的更改(在 DOM,而不是 React 状态)。然后任何祖先元素都可以根据需要对此行为采取行动或忽略它。父元素也可以从任何后代元素接收此调整大小事件,无论它嵌套的深度如何。它也不关心哪个后代元素发生了变化。如果您有多个可以更改的后代,这将触发其中任何一个。

此外,这里的调整大小事件 NOT 绑定到 window。 对子元素大小的任何 更改都会触发父元素上的事件侦听器。

就我个人而言,我会调用 Main 组件中的钩子并将子组件包装在 forwardRef (check docs here).

在此处查看完整示例:

Main.tsx

export default function Main(props: { notifications: Notification[] }) {
    const { notifications } = props;
    const [ref, dimensions] = useDimensions()

    return (
         <main>
            <Header ref={ref}/>
           {JSON.stringify(dimensions)}
        </main>
    )
}

这里做了什么,我们只是将 ref 向下传递给子组件,我们只是显示尺寸(测试目的)。

DefaultHeader.tsx

import { forwardRef } from "react";

const DefaultHeader = forwardRef((_, ref) => {
  return (
    <>
      <div ref={ref} className="header" >
        <h1>Hello!</h1>
      </div>
    </>
  );
});

export default DefaultHeader;

在这里,我们只是将 ref 附加到它之前所在的容器(您的示例)。

参见 this CodeSandbox 上的完整示例。

如果您需要更多解释,请告诉我。