问题描述
我正在实现一个菜单,当用户单击头像时该菜单将打开.问题在于菜单在完全不同的位置呈现:
I'm implementing a menu that opens when the user clicks on an Avatar. The problem is that the menu is rendering in a completely different place:
该头像是右边的绿色"OB"按钮.没有控制台错误,正在检查 Popover
元素,它正在接收 anchorEl
道具:
The avatar is the green "OB" button on the right. There is no console error and inspecting the Popover
element, it's receiving the anchorEl
prop:
化身右侧的语言菜单可以很好地渲染,并在应打开的位置打开.我的代码看起来不错,我真的不确定为什么位置错误:
The language menu, on the right of the avatar, renders just fine, opening where it should open. My code seems fine, I'm really not sure why the position is wrong:
export function DashboardNavbar({ setDrawer }) {
// translation hook
const { i18n } = useTranslation("navbar");
// config drawer state
const [configDrawer, setConfigDrawer] = useState(false);
// config menu state
const configMenuState = usePopupState({
variant: "popover",
popupId: "configMenu"
});
// avatar id
const [cookie] = useCookies("userInfo");
const decodedToken = decodeToken(cookie.userInfo.token);
const avatarId =
decodedToken.firstName.charAt(0) + decodedToken.lastName.charAt(0);
function DesktopNavbar() {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="lg">
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
{...bindTrigger(configMenuState)}
>
{avatarId}
</Avatar>
<DashboardMenu
bindMenu={bindMenu}
menuState={configMenuState}
/>
<LanguageMenu i18n={i18n} />
</div>
</Container>
</StyledDashboardNavbar>
</>
);
}
function MobileNavbar() {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="md">
<div className="navbar">
<div
style={{
display: "flex",
alignItems: "center"
}}
>
<MenuIcon
color="secondary"
onClick={() => setDrawer(true)}
/>
</div>
<div
className="logo"
onClick={() => setConfigDrawer(true)}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
>
{avatarId}
</Avatar>
</div>
</div>
</Container>
</StyledDashboardNavbar>
<AvatarDrawer
drawer={configDrawer}
setDrawer={setConfigDrawer}
/>
</>
);
}
return window.innerWidth > 480 ? <DesktopNavbar /> : <MobileNavbar />;
}
我正在使用 material-ui-popup-state
,但是我尝试在没有此程序包的情况下实现"on-hand",结果是相同的.
I'm using the material-ui-popup-state
, but I tried implementing "on-hand" without this package and the result was the same.
对此有任何帮助,我们深表感谢.预先感谢
Any help on this is appreciated. Thanks in advance
推荐答案
问题是 DesktopNavbar
在 DashboardNavbar
中的嵌套.这意味着每次 DashboardNavbar
重新渲染时,都会重新定义 DesktopNavbar
.由于与以前的 DashboardNavbar
渲染相比, DesktopNavbar
将是一个新功能,因此React无法将其识别为相同的组件类型,而 DesktopNavbar
将是相同的组件类型.重新安装而不是仅重新渲染.由于菜单状态保持在 DashboardNavbar
内,因此打开菜单会导致重新呈现 DashboardNavbar
,因此会重新定义 DesktopNavbar
,因此,由于重新安装了 DesktopNavbar
及其内部的所有内容,传递给菜单的锚元素将不再是DOM的一部分.
The problem is the nesting of DesktopNavbar
within DashboardNavbar
. This means that every time DashboardNavbar
re-renders, DesktopNavbar
will be redefined. Since DesktopNavbar
will be a new function compared to the previous render of DashboardNavbar
, React will not recognize it as the same component type and DesktopNavbar
will be re-mounted rather than just re-rendered. Since the menu state is maintained within DashboardNavbar
, opening the menu causes a re-render of DashboardNavbar
and therefore a re-definition of DesktopNavbar
so, due to the re-mounting of DesktopNavbar
and everything inside it, the anchor element passed to the menu will no longer be part of the DOM.
嵌套组件的定义几乎总是一个坏主意,因为每次重新渲染包含的组件时,嵌套的组件都会被视为一种新的元素类型.
It is almost always a bad idea to nest the definitions of components, because the nested components will be treated as a new element type with each re-render of the containing component.
来自 https://reactjs.org/docs/reconciliation.html#elements-of-different-types :
在重新渲染 DashboardNavbar
时重新定义 DesktopNavbar
和 MobileNavbar
时,这些元素中的整个DOM元素树将从中删除.并从头开始重新创建DOM,而不仅仅是将更改应用于现有DOM元素.这会对性能产生重大影响,还会引起行为问题,例如您遇到的行为问题,即您所引用的元素意外地不再是DOM的一部分.
When you redefine DesktopNavbar
and MobileNavbar
on re-render of DashboardNavbar
, the entire tree of DOM elements within those will be removed from the DOM and re-created from scratch rather than just applying changes to the existing DOM elements. This has a big performance impact and also causes behavior issues like the one you experienced where elements that you are referring to are unexpectedly no longer part of the DOM.
如果您改为将 DesktopNavbar
和 MobileNavbar
移至顶层,并将 DashboardNavbar
中的所有依赖项作为道具传递,则会导致 将被React识别为跨
DashboardNavbar
的重新渲染的一致组件类型. LanguageMenu
没有相同的问题,因为它的状态大概是在内部管理的,因此打开它不会导致 DashboardNavbar
的重新呈现.
If you instead move DesktopNavbar
and MobileNavbar
to the top-level and pass any dependencies from DashboardNavbar
as props, this will cause DesktopNavbar
to be recognized by React as a consistent component type across re-renders of DashboardNavbar
. LanguageMenu
doesn't have the same issue, because presumably its state is managed internally, so opening it doesn't cause a re-render of DashboardNavbar
.
示例代码重组(未执行,因此可能会有小错误):
Sample restructuring of code (not executed, so may have minor errors):
function DesktopNavbar({configMenuState, i18n}) {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="lg">
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
{...bindTrigger(configMenuState)}
>
{avatarId}
</Avatar>
<DashboardMenu
bindMenu={bindMenu}
menuState={configMenuState}
/>
<LanguageMenu i18n={i18n} />
</div>
</Container>
</StyledDashboardNavbar>
</>
);
}
function MobileNavbar({setDrawer, configDrawer, setConfigDrawer, avatarId}) {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="md">
<div className="navbar">
<div
style={{
display: "flex",
alignItems: "center"
}}
>
<MenuIcon
color="secondary"
onClick={() => setDrawer(true)}
/>
</div>
<div
className="logo"
onClick={() => setConfigDrawer(true)}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
>
{avatarId}
</Avatar>
</div>
</div>
</Container>
</StyledDashboardNavbar>
<AvatarDrawer
drawer={configDrawer}
setDrawer={setConfigDrawer}
/>
</>
);
}
export function DashboardNavbar({ setDrawer }) {
// translation hook
const { i18n } = useTranslation("navbar");
// config drawer state
const [configDrawer, setConfigDrawer] = useState(false);
// config menu state
const configMenuState = usePopupState({
variant: "popover",
popupId: "configMenu"
});
// avatar id
const [cookie] = useCookies("userInfo");
const decodedToken = decodeToken(cookie.userInfo.token);
const avatarId =
decodedToken.firstName.charAt(0) + decodedToken.lastName.charAt(0);
return window.innerWidth > 480 ? <DesktopNavbar configMenuState={configMenuState} i18n={i18n} /> : <MobileNavbar setDrawer={setDrawer} configDrawer={configDrawer} setConfigDrawer={setConfigDrawer} avatarId={avatarId} />;
}
解决此问题的另一种方法是只消除嵌套组件,以使 DashboardNavbar
是单个组件:
An alternative way to fix this is to just eliminate the nested components, so that DashboardNavbar
is a single component:
export function DashboardNavbar({ setDrawer }) {
// translation hook
const { i18n } = useTranslation("navbar");
// config drawer state
const [configDrawer, setConfigDrawer] = useState(false);
// config menu state
const configMenuState = usePopupState({
variant: "popover",
popupId: "configMenu"
});
// avatar id
const [cookie] = useCookies("userInfo");
const decodedToken = decodeToken(cookie.userInfo.token);
const avatarId =
decodedToken.firstName.charAt(0) + decodedToken.lastName.charAt(0);
const useDesktopLayout = window.innerWidth > 480;
return <>
{useDesktopLayout &&
<StyledDashboardNavbar>
<Container maxWidth="lg">
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
{...bindTrigger(configMenuState)}
>
{avatarId}
</Avatar>
<DashboardMenu
bindMenu={bindMenu}
menuState={configMenuState}
/>
<LanguageMenu i18n={i18n} />
</div>
</Container>
</StyledDashboardNavbar>
}
{!useDesktopLayout &&
<>
<StyledDashboardNavbar>
<Container maxWidth="md">
<div className="navbar">
<div
style={{
display: "flex",
alignItems: "center"
}}
>
<MenuIcon
color="secondary"
onClick={() => setDrawer(true)}
/>
</div>
<div
className="logo"
onClick={() => setConfigDrawer(true)}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
>
{avatarId}
</Avatar>
</div>
</div>
</Container>
</StyledDashboardNavbar>
<AvatarDrawer
drawer={configDrawer}
setDrawer={setConfigDrawer}
/>
</>
}
</>;
}
相关答案:
这篇关于弹出菜单与锚元素的渲染位置不同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!