【问题标题】:How to use custom functional components within Material-UI Menu如何在 Material-UI 菜单中使用自定义功能组件
【发布时间】:2019-10-11 22:10:01
【问题描述】:

我正在使用 Material-UI 来设计我正在构建的 React 应用程序。我正在尝试在 <Menu> 组件中使用我自己的自定义功能组件,但出现了一个奇怪的错误:

Warning: Function components cannot be given refs.
Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?

Check the render method of ForwardRef(Menu).
    in TextDivider (at Filter.js:32)

发生这种情况的 JSX 如下所示:(第 32 行是 TextDivider 组件)

<Menu>
    <TextDivider text="Filter Categories" />
    <MenuItem>...</MenuItem>
</Menu>

TextDivider 在哪里:

const TextDivider = ({ text }) => {
    const [width, setWidth] = useState(null);
    const style = {
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center'
    };

    const hrStyle = {
        width: `calc(100% - ${width}px)`,
        marginRight: 0,
        marginLeft: 0
    };

    const spanStyle = {
        color: '#555',
        whiteSpace: 'nowrap'
    };

    const ref = createRef();
    useEffect(() => {
        if (ref.current) {
            const width = ref.current.getBoundingClientRect().width + 35;
            console.log('width', width);
            setWidth(width);
        }
    }, [ref.current]);

    return (
        <div style={style}>
            <span ref={ref} style={spanStyle}>{ text }</span>
            <Divider className="divider-w-text" style={ hrStyle }/>
        </div>
    );
};

奇怪的是,这个组件在常规容器中自行设置时渲染得非常好,但似乎只有在 &lt;Menu&gt; 组件内渲染时才会抛出这个错误。

此外,我发现如果我像这样将 TextDivider 包装在一个 div 中,这个错误就完全消失了,这真的很奇怪:

<Menu>
    <div>
        <TextDivider text="Filter Categories" />
    </div>
    <MenuItem>...</MenuItem>
</Menu>

但是,这对我来说就像是胶带,我宁愿理解为什么我不能只在菜单中呈现这个组件的根本问题。我没有在我的功能组件中使用任何类型的 Material-UI 引用,也没有在 &lt;TextDivider&gt; 组件中的 &lt;Divider&gt; 组件上放置引用,所以我不明白它为什么会抛出这个错误。

如果您能深入了解为什么会发生这种行为,我们将不胜感激。

我尝试过使用 useCallback 钩子、createRef()useRef(),并阅读了关于在功能组件中使用钩子的所有内容。该错误本身似乎具有误导性,因为即使反应文档本身也显示使用功能组件在这里引用:https://reactjs.org/docs/hooks-reference.html#useref

【问题讨论】:

标签: reactjs material-ui react-hooks


【解决方案1】:

Menu 使用 Menu 的第一个子项作为 Menu 内部使用的 Popover 组件的“内容锚”。 “内容锚点”是菜单内的 DOM 元素,Popover 尝试与锚元素(菜单外的元素,作为定位菜单的参考点)对齐。

为了利用第一个子元素作为内容锚点,Menu 向它添加了一个引用(使用 cloneElement)。为了不收到您收到的错误(并且为了使定位正常工作),您的功能组件需要将 ref 转发到它呈现的组件之一(通常是最外层的组件 - 在您的情况下为 div)。

当您使用 div 作为 Menu 的直接子代时,您不会收到错误消息,因为 div 可以成功接收到 ref。

SakoBu 引用的文档包含有关如何将 ref 转发到函数组件的详细信息。

结果应该如下所示(但我是在手机上回答这个问题,如果有任何语法错误,请见谅):

const TextDivider = React.forwardRef(({ text }, ref) => {
    const [width, setWidth] = useState(null);
    const style = {
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center'
    };

    const hrStyle = {
        width: `calc(100% - ${width}px)`,
        marginRight: 0,
        marginLeft: 0
    };

    const spanStyle = {
        color: '#555',
        whiteSpace: 'nowrap'
    };
    // Separate bug here.
    // This should be useRef instead of createRef.
    // I’ve also renamed this ref for clarity, and used "ref" for the forwarded ref.
    const spanRef = React.useRef();
    useEffect(() => {
        if (spanRef.current) {
            const width = spanRef.current.getBoundingClientRect().width + 35;
            console.log('width', width);
            setWidth(width);
        }
    }, [spanRef.current]);

    return (
        <div ref={ref} style={style}>
            <span ref={spanRef} style={spanStyle}>{ text }</span>
            <Divider className="divider-w-text" style={ hrStyle }/>
        </div>
    );
});

您可能还会发现以下答案很有用:

What's the difference between `useRef` and `createRef`?

【讨论】:

  • 感谢您清楚地解释了这个问题的根本问题!你能解释一下你是如何发现菜单的第一个孩子应该接收这个内容锚引用的吗?我是否在 Menu 组件的文档中错过了这一点?
  • 我不是核心团队的一员,但我是 Material-UI 的积极贡献者,我对 v4 的 Menu 进行了重大改进,因此我非常熟悉它的工作原理内部。
猜你喜欢
  • 2020-06-30
  • 1970-01-01
  • 2020-04-16
  • 2021-10-09
  • 2018-11-23
  • 2022-08-15
  • 2021-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多