Ota*_*der 5 javascript reactjs material-ui
我正在实现一个当用户单击头像时打开的菜单。问题是菜单呈现在完全不同的位置:
头像是右侧绿色的“OB”按钮。没有控制台错误并检查Popover元素,它正在接收道具anchorEl:
头像右侧的语言菜单渲染得很好,在应该打开的地方打开。我的代码看起来不错,我真的不知道为什么这个位置是错误的:
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 />;
}
Run Code Online (Sandbox Code Playgroud)
我正在使用material-ui-popup-state,但我尝试在没有这个包的情况下“现场”实现,结果是相同的。
对此的任何帮助表示赞赏。提前致谢
问题是DesktopNavbarinside的嵌套DashboardNavbar。这意味着每次DashboardNavbar重新渲染,DesktopNavbar都会被重新定义。由于DesktopNavbar与之前的渲染相比,这将是一个新函数DashboardNavbar,React 不会将其识别为相同的组件类型,并且DesktopNavbar将重新挂载而不仅仅是重新渲染。由于菜单状态保持在 中DashboardNavbar,打开菜单会导致重新渲染DashboardNavbar,因此重新定义DesktopNavbar,因此,由于重新安装DesktopNavbar以及其中的所有内容,传递给菜单的锚元素将不再是一部分DOM 的。
嵌套组件的定义几乎总是一个坏主意,因为每次重新渲染包含组件时,嵌套组件都会被视为新的元素类型。
来自https://reactjs.org/docs/reconciliation.html#elements-of- Different-types :
每当根元素具有不同类型时,React 就会拆除旧树并从头开始构建新树。从
<a>到<img>、或从<Article>到<Comment>、或从<Button>到<div>- 其中任何一个都将导致完全重建。
当您重新定义DesktopNavbar并MobileNavbar重新渲染DashboardNavbar时,其中的整个 DOM 元素树将从 DOM 中删除并从头开始重新创建,而不是仅对现有 DOM 元素应用更改。这会对性能产生很大影响,还会导致行为问题,例如您所遇到的问题,即您所引用的元素意外地不再是 DOM 的一部分。
如果您将DesktopNavbar和移动MobileNavbar到顶层并从DashboardNavbarprops 传递任何依赖项,这将导致DesktopNavbarReact 将 和 重新渲染为一致的组件类型DashboardNavbar。LanguageMenu没有同样的问题,因为大概它的状态是内部管理的,所以打开它不会导致DashboardNavbar.
代码重构示例(未执行,因此可能有小错误):
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} />;
}
Run Code Online (Sandbox Code Playgroud)
解决此问题的另一种方法是消除嵌套组件,这样它DashboardNavbar就是一个组件:
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}
/>
</>
}
</>;
}
Run Code Online (Sandbox Code Playgroud)
相关回答:
| 归档时间: |
|
| 查看次数: |
5555 次 |
| 最近记录: |