当前位置: 首页 > 面试题库 >

弹出菜单与锚元素的渲染位置不同

朱淮晨
2023-03-14
问题内容

我正在实现一个菜单,当用户单击头像时该菜单将打开。问题在于菜单在完全不同的位置呈现:

错误的位置

化身是右边的绿色“ 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 />;
}

我正在使用material-ui-popup-state,但是我尝试在没有此程序包的情况下“现场”实现,结果是相同的。

在这方面的任何帮助表示赞赏。提前致谢


问题答案:

问题是DesktopNavbar内部嵌套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>-这些都将导致完全重建。

当您重新定义DesktopNavbarMobileNavbar重新渲染时DashboardNavbar,其中的DOM元素的整个树将从DOM中删除并重新创建,而不仅仅是对现有DOM元素进行更改。这会对性能产生重大影响,并且还会引起行为问题,例如您所遇到的行为问题,即您所引用的元素意外地不再是DOM的一部分。

如果改为移动DesktopNavbar,并MobileNavbar在顶层,并通过从任何依赖DashboardNavbar作为道具,这将导致DesktopNavbar被认可作出反应,一致的组件类型的跨重新呈现DashboardNavbarLanguageMenu不会出现相同的问题,因为大概是在内部对其状态进行管理,因此打开它不会导致重新呈现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} />;
}

解决此问题的另一种方法是只消除嵌套的组件,因此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}
                />
            </>
    }
    </>;
}


 类似资料:
  • 主要内容:实例,实例,实例,实例元素是构成 React 应用的最小单位,它用于描述屏幕上输出的内容。 与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。 将元素渲染到 DOM 中 首先我们在一个 HTML 页面中添加一个 id="example" 的 <div>: 在此 div 中的所有内容都将由 React DOM 来管

  • 我想知道HTML代码是否有效: 如果它无效,还能做什么?

  • 我有一个弹出窗口,其中包含不同种类的内容,分为几个部分,点击一个按钮就会触发。我试图在此实现中公开适当的可访问性/aria语义。(它不是一个模式对话框,而是一个简单的就地弹出窗口)。根据我目前的研究,我认为这是有意义的: 触发按钮上的咏叹调展开 在触发按钮上 其他可选键盘可访问性 此菜单按钮实现示例:https://www.w3.org/TR/wai-aria-practices/examples

  • mui框架内置了弹出菜单插件,弹出菜单显示内容不限,但必须包裹在一个含.mui-popover类的div中,如下即为一个弹出菜单内容: <div id="popover" class="mui-popover"> <ul class="mui-table-view"> <li class="mui-table-view-cell"><a href="#">Item1</a></li

  • 我使用onCreateOptionsMenu创建了一个溢出菜单,但该菜单几乎完全位于屏幕之外。我尝试过显式设置(如操作栏菜单显示屏幕中的建议),但这并没有解决我的问题。我也不想遵循使用旧主题的建议(如在ICS-Menu item text Cutoff?)。 XML是 我做错了什么?

  • 因此,我试图用几个HTML输入创建一个动态表单。我有一个对象数组,其中包含应该呈现的的。目前,我能够呈现两个输入,如文本区域,但我如何处理收音机,复选框,选择连同他们的选项?任何帮助都将不胜感激。 请参见此CodeSandBox。