在 Vue 3 中单击 router-link 时如何在不重新加载页面的情况下切换侧边栏

How to toggle sidebar without page reload when router-link is clicked in Vue 3

我有一个类似于模式的边栏组件。单击按钮时,侧边栏会转换为带有导航 links 的视口。这些导航 links 实际上是连接到 vue-router 的 router-links。

我想要完成的事情

当我单击侧边栏组件内的路由器-link 时,我希望侧边栏从视口过渡并且我希望单击的路由器-link 的组件呈现无需重新加载页面。

当前情况

当我点击 router-link 时,侧边栏立即从 DOM 中删除。它不会按预期翻译出屏幕。此外,页面已重新加载。

我还尝试了什么

我还尝试将 <transition> 包装器连同关联的 CSS 类 一起移动到 TheSidebar.vue 组件中,然后我将 sidebarIsVisible 作为道具从App.vueTheSidebar.vue.

我的代码

可以找到 Codesandbox 演示 here

App.vue

<template>
  <router-view></router-view>
  <button @click="toggleSidebar" class="toggleBtn">Toggle Sidebar</button>
  <transition name="sidebar">
    <the-sidebar
      v-if="sidebarIsVisible"
      @link-clicked="toggleSidebar"
    ></the-sidebar>
  </transition>
</template>

<script>
import TheSidebar from "./components/TheSidebar.vue";

export default {
  components: {
    TheSidebar,
  },
  data() {
    return {
      sidebarIsVisible: false,
    };
  },
  methods: {
    toggleSidebar() {
      this.sidebarIsVisible = !this.sidebarIsVisible;
    },
    closeSidebar() {
      this.sidebarIsVisible = false;
    },
  },
};
</script>

<style>
/* basic styling */

.toggleBtn {
  position: fixed;
  top: 5px;
  left: 5px;
}

.sidebar-enter-active {
  animation: slide-sidebar 0.3s ease;
}

.sidebar-leave-active {
  animation: slide-sidebar 0.3s ease reverse;
}

@keyframes slide-sidebar {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}
</style>

TheSidebar.vue

<template>
  <div class="sidebar">
    <nav>
      <ul>
        <li>
          <router-link @click="$emit('link-clicked')" to="/link1">
            Link 1
          </router-link>
        </li>
        <li>
          <router-link @click="$emit('link-clicked')" to="/link2">
            Link 2
          </router-link>
        </li>
      </ul>
    </nav>
  </div>
</template>

<script>
export default {
  emits: ["link-clicked"],
};
</script>

<style scoped>
/* basic styling */
</style>

main.js

import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue";
import LinkOne from "./components/LinkOne.vue";
import LinkTwo from "./components/LinkTwo.vue";

const app = createApp(App);
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: "/link1", component: LinkOne },
    { path: "/link2", component: LinkTwo }
  ]
});

app.use(router);

app.mount("#app");

这里有几件事我不确定,但我会尝试解释我认为正在发生的事情。

首先,router-link 上的点击事件是导致页面重新加载的原因,但我在文档中找不到任何提及此预期行为的内容(它可能值得 opening an issue on the GitHub repo).

解决此问题的方法是使用 event-delegation,方法是将事件处理程序移动到 ul 上,并创建一个方法来确定 link 是否已被单击(下面的示例) .

其次,这就是事情变得奇怪的地方,在 VSCode 中,在子组件的发射事件中使用 kebab-case 似乎可以防止发射任何东西,因此将它们更改为 camelCase 可以解决这个问题。但是,在 CodeSandbox 中尝试这样做根本行不通,并且 ESLint 抱怨发出的事件应该是 kebab-case。因此,在 CodeSandbox 中,情况恰恰相反:发出的事件名称应该是 kebab-case,而监听器应该是驼峰命名!完全不知道为什么这违背了 what the docs say on casing:

...we recommend using kebab-cased event listeners when you are using in-DOM templates.

同样,我在文档中找不到任何明确说明在 发出 事件时需要使用驼峰式大小写的内容,它只是说在 [=43] 时首选 kebab-case =]监听一个事件。


所以,总而言之,为了让您的代码在 VSCode 中工作并且遵循文档推荐的方式,您需要将其更改为:

<template>
    <div class="sidebar">
        <nav>
            <!-- Move event here -->
            <ul @click="handleClick($event)">
                <li>
                    <router-link to="/link1">
                        Link 1
                    </router-link>
                </li>
                <li>
                    <router-link to="/link2">
                        Link 2
                    </router-link>
                </li>
            </ul>
        </nav>
    </div>
</template>

<script>
export default {
    emits: ['linkClicked'], // Use camelCase for emitting

    methods: {
        handleClick(e) {
            // Check the target is a link being clicked
            if (e.target.localName !== 'a') return

            this.$emit('linkClicked')
        }
    }
}
</script>

保持 App.vue 与现有的完全一样,应该 有效。


为了让您的代码在 CodeSandbox 中工作,请交换大小写:

...
    emits: ['link-clicked'], // Use kebab-case for emitting
...
            this.$emit('link-clicked')
...

App.vue:

@linkClicked="toggleSidebar"

Working example.


如果有人能阐明这一点,那就太好了,因为我对这里发生的事情一头雾水。