如何在Material-UI菜单中使用自定义功能组件

10

我正在使用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>
    );
};

奇怪的是,当在常规容器中单独设置此组件时,它可以完美地呈现,但似乎只有在<Menu>组件内渲染时才会出现此错误。

此外,我发现如果将TextDivider包装在一个div中,这个错误就会完全消失:

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

然而,对我来说,这感觉像是用胶带临时修补,我更想了解为什么我不能只在菜单中渲染此组件的根本问题。我在我的函数组件中没有使用任何Material-UI ref,在我的组件中也没有将ref放置在组件上,所以我不明白为什么会引发此错误。

非常感谢您能提供有关此行为发生原因的任何见解。

我尝试过使用useCallback hook、createRef()、useRef(),并阅读了我能找到的所有有关在函数组件中使用hook的内容。错误本身似乎是误导性的,因为即使是React官方文档也展示了在函数组件中使用refs的情况:https:// reactjs.org / docs / hooks-reference.html#useref


它告诉你问题所在...这里是文档链接:https://reactjs.org/docs/forwarding-refs.html - SakoBu
1个回答

17

Menu将第一个子元素用作Popover组件的“内容锚点”,该组件在Menu内部使用。 "content anchor"是菜单中的DOM元素,Popover试图与锚点元素(菜单外部的元素,用于定位菜单的参考点)对齐。

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

当您使用div作为

的直接子代时,您不会遇到错误,因为
可以成功接收ref。

SakoBu提到的文档包含有关如何将refs转发给函数组件的详细信息。

结果应该类似于以下内容(但我是在手机上回答的,所以如果有任何语法错误,我很抱歉):

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>
    );
});

你可能会发现以下答案也很有用:

`useRef`和`createRef`之间的区别是什么?


感谢您清楚地解释了这个问题的根本原因!您能否解释一下您是如何发现菜单的第一个子元素应该接收此内容锚点引用的?我在菜单组件的文档中错过了吗? - FooBar
4
我不是核心团队成员,但我是Material-UI的积极贡献者,并针对 v4 对Menu做了重大改进,因此我非常熟悉其内部工作原理。 - Ryan Cogswell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接