如何在 Material-UI 菜单中使用自定义功能组件
How to use custom functional components within Material-UI Menu
我正在使用 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 并且我没有在我的 <TextDivider>
组件中的 <Divider>
组件上放置一个 ref 所以我不明白为什么会抛出这个错误。
如果能深入了解此行为发生的原因,我们将不胜感激。
我已经尝试使用 useCallback
挂钩、createRef()
、useRef()
,并阅读了我能找到的关于在功能组件中使用挂钩的所有内容。该错误本身似乎具有误导性,因为即使是 React 文档本身也显示在此处使用功能组件引用:https://reactjs.org/docs/hooks-reference.html#useref
Menu
使用 Menu 的第一个子项作为 Menu 内部使用的 Popover 组件的 "content anchor"。 "content anchor" 是菜单中的 DOM 元素,Popover 试图与锚元素(菜单外部的元素,是定位菜单的参考点)对齐。
为了利用第一个子项作为内容锚点,Menu 为其添加了一个 ref(使用 cloneElement)。为了不得到您收到的错误(并且为了定位正常工作),您的功能组件需要将 ref 转发到它呈现的组件之一(通常是最外面的组件 - 在您的情况下为 div) .
当您使用 div
作为 Menu
的直接子级时,您不会收到错误,因为 div 可以成功接收引用。
SakoBu 引用的文档包含有关如何将引用转发给函数组件的详细信息。
结果应该如下所示(但我是在 phone 上回答这个问题,如果有任何语法错误,我们深表歉意):
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>
);
});
您可能还会发现以下答案很有用:
我正在使用 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 并且我没有在我的 <TextDivider>
组件中的 <Divider>
组件上放置一个 ref 所以我不明白为什么会抛出这个错误。
如果能深入了解此行为发生的原因,我们将不胜感激。
我已经尝试使用 useCallback
挂钩、createRef()
、useRef()
,并阅读了我能找到的关于在功能组件中使用挂钩的所有内容。该错误本身似乎具有误导性,因为即使是 React 文档本身也显示在此处使用功能组件引用:https://reactjs.org/docs/hooks-reference.html#useref
Menu
使用 Menu 的第一个子项作为 Menu 内部使用的 Popover 组件的 "content anchor"。 "content anchor" 是菜单中的 DOM 元素,Popover 试图与锚元素(菜单外部的元素,是定位菜单的参考点)对齐。
为了利用第一个子项作为内容锚点,Menu 为其添加了一个 ref(使用 cloneElement)。为了不得到您收到的错误(并且为了定位正常工作),您的功能组件需要将 ref 转发到它呈现的组件之一(通常是最外面的组件 - 在您的情况下为 div) .
当您使用 div
作为 Menu
的直接子级时,您不会收到错误,因为 div 可以成功接收引用。
SakoBu 引用的文档包含有关如何将引用转发给函数组件的详细信息。
结果应该如下所示(但我是在 phone 上回答这个问题,如果有任何语法错误,我们深表歉意):
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>
);
});
您可能还会发现以下答案很有用: