共享导航栏组件的精简页面/组件缓存问题

问题描述 投票:0回答:1

我目前正在开发一个网站,遇到了一个小问题。

===

重现步骤:

1.

npm create svelte@latest test
(骨架、打字稿、eslint-prettier-playwright-vitest)

2.

cd test

3.

npm i

4.

npm run dev -- --open
(在浏览器中打开进行调试)

5.创建

src/components/Navbar.svelte
,包含以下内容:

<script lang="ts">
    import { onMount } from 'svelte';

    function toggleHamburgerMenu() {
        var x = document.getElementById('myLinks') as HTMLDivElement;
        if (x.style.display === 'block') {
            x.style.display = 'none';
        } else {
            x.style.display = 'block';
        }
    }

    function getTranslatedPagePath(): string {
        switch (window.location.pathname) {
            case '/':
                return 'nav.about';
            case '/services':
                return 'nav.services';
            case '/updates':
                return 'nav.updates';
            case '/photos':
                return 'nav.photos';
            case '/contact':
                return 'nav.contact';
            case '/links':
                return 'nav.links';
            default:
                return '';
        }
    }

    $: currentTab = 'nav.about';
    onMount(() => {
        currentTab = getTranslatedPagePath();

        function closeTopnav(this: HTMLAnchorElement) {
            var x = document.getElementById('myLinks') as HTMLDivElement;
            x.style.display = 'none';

            currentTab = this.innerHTML;
        }

        // topnav doesnt close automatically when clicking on a link
        const links = document.getElementsByClassName('topnav_link') as unknown as HTMLAnchorElement[];
        for (let i = 0; i < links.length; ++i) {
            links[i].addEventListener('click', closeTopnav);
        }

        // hide topnav when clicking outside
        document.addEventListener('click', (e: MouseEvent) => {
            var x = document.getElementById('topnav-wrapper') as HTMLDivElement;
            var y: Node = e.target as Node;
            var z = document.getElementById('myLinks') as HTMLDivElement;
            if (!x.contains(y) && z.style.display == 'block') {
                z.style.display = 'none';
            }
        });
    });
</script>

<head>
    <link
        rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
    />
</head>

<div id="topnav-wrapper">
    <div>
        <a href="/"><img src="" alt="Logo" /></a>
    </div>
    <div class="topnav">
        <div id="content">
            <a href="" class="active">{currentTab}</a>
            <a href="" class="icon" on:click={toggleHamburgerMenu}>
                <i class="fa fa-bars"></i>
            </a>
        </div>
        <div id="myLinks">
            {#each 'about, services, updates, photos, contact, links'.split(', ') as link}
                {#if `nav.${link}` !== currentTab}
                    <a class="topnav_link" href="/{link === 'about' ? '' : link}">{`nav.${link}`}</a>
                {/if}
            {/each}
        </div>
    </div>
</div>

<style>
    #topnav-wrapper {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;

        position: fixed;
        top: 0;
        left: 0;
        width: 100%;

        overflow: visible;

        z-index: 100;

        /* background-color: rgba(255, 255, 255, 0.6); */
        margin: 0;
        padding: 0;
    }

    .topnav {
        overflow: hidden;
        /* background-color: #333; */

        /* width: fit-content; */
        /* height: fit-content; */

        margin: 1.5rem;
    }

    /* Hide the links inside the navigation menu (except for logo/home) */
    .topnav #myLinks {
        display: none;

        position: absolute;
        top: inherit;
        left: inherit;
        background-color: #333;
        pointer-events: all;
    }

    /* Style navigation menu links */
    .topnav a {
        color: white;
        padding: 14px 16px;
        text-decoration: none;
        display: block;

        /* width: fit-content; */
        background-color: rgba(255, 255, 255, 0.74);
        color: black;
        font-size: 1.5rem;
    }

    /* Style the hamburger menu */
    .topnav a.icon {
        background: black;
        display: block;
    }

    /* Add a grey background color on mouse-over */
    .topnav a:hover {
        background-color: #ddd;
        color: black;
    }

    /* Style the active link (or home/logo) */
    .active {
        background-color: rgb(135, 206, 235);
        color: white;
    }

    img {
        position: absolute;
        top: 0;
        left: 0;
    }

    #content {
        display: flex;
    }

    #logo {
        background-color: white;
        opacity: 70%;
    }

    @media screen and (min-width: 320px) {
        #logo,
        img {
            width: 115px;
            height: 50px;
            margin-left: 15px;
        }
        img {
            top: 20px;
        }
    }

    @media screen and (min-width: 520px) {
        #logo,
        img {
            width: 135px;
            height: 55px;
        }
        img {
            top: 20px;
        }
    }

    @media screen and (min-width: 720px) {
        #logo,
        img {
            width: 155px;
            height: 65px;
            margin-top: 10px;
        }
        img {
            top: 15px;
        }
    }

    @media screen and (min-width: 920px) {
        #logo,
        img {
            width: 175px;
            height: 70px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1120px) {
        #logo,
        img {
            width: 195px;
            height: 75px;
            margin-top: 20px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1320px) {
        #logo,
        img {
            width: 215px;
            height: 80px;
        }
        img {
            margin-top: 5px;
        }
    }

    @media screen and (min-width: 1520px) {
        #logo,
        img {
            width: 235px;
            height: 90px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1720px) {
        #logo,
        img {
            width: 255px;
            height: 100px;
            margin-top: 20px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1920px) {
        #logo,
        img {
            width: 275px;
            height: 110px;
            margin-top: 20px;
        }
        img {
            top: -10px;
        }
    }

    img {
        top: 0px; /* TODO: responsive */
        margin-top: 0px;
    }

    #content a {
        background-color: transparent;
        color: black;
        font-size: 1.5rem;
    }
</style>

6.创建

src/routes/+layout.svelte

<script>
    import Navbar from '../components/Navbar.svelte';
</script>

<Navbar />
<slot />

7.创建

/(about), services, updates, photos, contact, links
路线,使用简单的
h1
提及页面标题,以便轻松了解我们所在的路线。

8.转到浏览器选项卡,打开右上角的汉堡菜单,单击每个链接。之后,如果您尝试单击其中任何一个,标题将不会更新,链接列表将不会更新并删除当前页面链接,并且汉堡菜单将不会关闭(但我们将转到正确的页面)。

===

我遇到的问题是,在导航栏中的每个链接第一次迭代之后,它们会被缓存(或者类似的东西,我不太确定 svelte 的底层机制),当我尝试再次单击它们时:

  1. 导航栏当前选项卡标题未更新,
  2. 链接没有更新,所以我看到了我不应该看到的当前选项卡的链接

这两个问题的共同点是 currentTab,我试图将其设为一个简单的 var 来查看它是否改变任何内容,但它没有改变,所以我有点没有想法了。

有人知道如何解决这个问题吗?

预期:

  1. 导航栏组件在每个
    <a>
    链接点击时更新,因此标题和链接列表更新(并且汉堡菜单关闭)。
javascript html svelte
1个回答
0
投票

好吧,首先感谢您提供的非常容易重现的示例和说明,它很有帮助。我将带您解决这些问题,然后留下我自己的意见。

您在

.content
div 中使用带有空 href 属性的 nchor 标签作为按钮,这会触发 SvelteKit 中的导航事件,这可能是问题的一部分。我的建议是简单地将其切换为简单的按钮并相应地设置它们的样式。 关于仅在第一次迭代中运行的问题有两个问题。首先,在
closeTopnav()
回调中声明的
onMount
函数可能会使其在首次调用后无法访问,因此我将其移至顶层。其次,在
onMount
中的每个链接上附加事件侦听器时的回调似乎是核心问题。通过使用链接本身中的
on:click
Svelte 处理程序调用该函数,可以轻松解决此问题,这是您的代码,其中包含我更改的内容的注释。

<script lang="ts">
    import { onMount } from 'svelte';

    function toggleHamburgerMenu() {
        var x = document.getElementById('myLinks') as HTMLDivElement;
        if (x.style.display === 'block') {
            x.style.display = 'none';
        } else {
            x.style.display = 'block';
        }
    }

    function closeTopnav(thisTab:string) {
        var x = document.getElementById('myLinks') as HTMLDivElement;
        x.style.display = 'none';

        // I usually avoid .innerHTML and opt for .textContent, but here you can pass the string iself as an argument just like the name of the link itself
        currentTab = thisTab;
    }

    let currentTab:string = 'nav.about';

    onMount(() => {
        //scoped this function to the onMount since this function is only used to find the path when it's mounted
        function getTranslatedPagePath(): string {
            switch (window.location.pathname) {
                case '/':
                    return 'nav.about';
                case '/services':
                    return 'nav.services';
                case '/updates':
                    return 'nav.updates';
                case '/photos':
                    return 'nav.photos';
                case '/contact':
                    return 'nav.contact';
                case '/links':
                    return 'nav.links';
                default:
                    return '';
            }
        }
        currentTab = getTranslatedPagePath();

        // hide topnav when clicking outside
        document.addEventListener('click', (e: MouseEvent) => {
            var x = document.getElementById('topnav-wrapper') as HTMLDivElement;
            var y: Node = e.target as Node;
            var z = document.getElementById('myLinks') as HTMLDivElement;
            if (!x.contains(y) && z.style.display == 'block') {
                z.style.display = 'none';
            }
        });
    });

</script>

<head>
    <link
        rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
    />
</head>

<div id="topnav-wrapper">
    <div>
        <a href="/"><img src="" alt="Logo" /></a>
    </div>
    <div class="topnav">
        <div id="content">
            <div class="active">{currentTab}</div>
            <!-- changed href-less anchors to button -->
            <button class="icon" on:click={toggleHamburgerMenu}>
                <i class="fa fa-bars"></i>
            </button>
        </div>
        <div id="myLinks">
            {#each 'about, services, updates, photos, contact, links'.split(', ') as link}
                {#if `nav.${link}` !== currentTab}
                    <!-- added click handler in the link itself, passing the concatenated string as argument -->
                    <a class="topnav_link" href="/{link === 'about' ? '' : link}"
                        on:click={() => {closeTopnav(`nav.${link}`)}}
                    >
                        {`nav.${link}`}
                    </a>
                {/if}
            {/each}
        </div>
    </div>
</div>

<style>
    #topnav-wrapper {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;

        position: fixed;
        top: 0;
        left: 0;
        width: 100%;

        overflow: visible;

        z-index: 100;

        /* background-color: rgba(255, 255, 255, 0.6); */
        margin: 0;
        padding: 0;
    }

    .topnav {
        overflow: hidden;
        /* background-color: #333; */

        /* width: fit-content; */
        /* height: fit-content; */

        margin: 1.5rem;
    }

    /* Hide the links inside the navigation menu (except for logo/home) */
    .topnav #myLinks {
        display: none;

        position: absolute;
        top: inherit;
        left: inherit;
        background-color: #333;
        pointer-events: all;
    }

    /* Style navigation menu links */

    /* added button selector */
    .topnav button, .topnav a {
        color: white;
        padding: 14px 16px;
        text-decoration: none;
        display: block;
        border: none;
        /* width: fit-content; */
        background-color: rgba(255, 255, 255, 0.74);
        color: black;
        font-size: 1.5rem;
    }

    /* Style the hamburger menu */
    /* changed to button selector */
    .topnav button.icon {
        background: black;
        display: block;
    }

    /* Add a grey background color on mouse-over */
    /* changed to button selector */
    .topnav button:hover {
        background-color: #ddd;
        color: black;
    }

    /* Style the active link (or home/logo) */
    .active {
        background-color: rgb(135, 206, 235);
        color: white;
    }

    img {
        position: absolute;
        top: 0;
        left: 0;
    }

    #content {
        display: flex;
    }

    #logo {
        background-color: white;
        opacity: 70%;
    }

    @media screen and (min-width: 320px) {
        #logo,
        img {
            width: 115px;
            height: 50px;
            margin-left: 15px;
        }
        img {
            top: 20px;
        }
    }

    @media screen and (min-width: 520px) {
        #logo,
        img {
            width: 135px;
            height: 55px;
        }
        img {
            top: 20px;
        }
    }

    @media screen and (min-width: 720px) {
        #logo,
        img {
            width: 155px;
            height: 65px;
            margin-top: 10px;
        }
        img {
            top: 15px;
        }
    }

    @media screen and (min-width: 920px) {
        #logo,
        img {
            width: 175px;
            height: 70px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1120px) {
        #logo,
        img {
            width: 195px;
            height: 75px;
            margin-top: 20px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1320px) {
        #logo,
        img {
            width: 215px;
            height: 80px;
        }
        img {
            margin-top: 5px;
        }
    }

    @media screen and (min-width: 1520px) {
        #logo,
        img {
            width: 235px;
            height: 90px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1720px) {
        #logo,
        img {
            width: 255px;
            height: 100px;
            margin-top: 20px;
        }
        img {
            top: 10px;
        }
    }

    @media screen and (min-width: 1920px) {
        #logo,
        img {
            width: 275px;
            height: 110px;
            margin-top: 20px;
        }
        img {
            top: -10px;
        }
    }

    img {
        top: 0px; /* TODO: responsive */
        margin-top: 0px;
    }

    /* changed to button selector */
    #content button {
        background-color: transparent;
        color: black;
        font-size: 1.5rem;
    }
</style>

IMO,您在 Svelte 为您提供良好替代方案的地方使用 Vanilla JS,这绝对没有问题,但生活可以更好。其中一些是在 onMount 中使用 for 循环手动附加事件监听器,甚至可以通过在顶层声明相同的函数(如

function hideTopnavWhenClickingOutside() {...}
)并调用 Svelte 的文档伪元素来处理文档事件监听器,如下所示:

<svelte:document on:click={hideTopnavWhenClickingOutside} />

但是学习 Svelte 的一大乐趣在于意识到它可以使开发变得多么容易和快捷,所以我希望这会有所帮助,并且它会让您的编码之旅变得越来越愉快!

© www.soinside.com 2019 - 2024. All rights reserved.