在Vue 3中单击路由器链接时如何在不重新加载页面的情况下切换侧边栏

Posted

技术标签:

【中文标题】在Vue 3中单击路由器链接时如何在不重新加载页面的情况下切换侧边栏【英文标题】:How to toggle sidebar without page reload when router-link is clicked in Vue 3 【发布时间】:2021-05-02 20:08:31 【问题描述】:

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

我想要完成的事情

当我单击侧边栏组件内的路由链接时,我希望侧边栏从视口过渡,并且我希望单击的路由链接的组件在不重新加载页面的情况下呈现。

目前正在发生的事情

当我单击路由器链接时,侧边栏会立即从 DOM 中删除。它不会按预期翻译出屏幕。此外,页面会重新加载。

我还尝试了什么

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

我的代码

可以在here找到 Codesandbox 演示

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");

【问题讨论】:

【参考方案1】:

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

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

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

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

...当您使用 DOM 内模板时,我们建议使用 kebab 封装的事件侦听器。

再次,我在文档中找不到任何明确说明发出事件时需要使用camelCase的内容,它只是说侦听时首选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.


如果有人能对此有所了解,那就太好了,因为我完全不知道这里发生了什么。

【讨论】:

这太棒了!标记为答案。我真的在为此苦苦挣扎,最终将它转向另一个更“hacky”的方向,甚至出现了一些问题。我将在此基础上进行重构。基本上我所做的是我删除了路由器链接并添加了将路由器链接路径作为参数传递的点击侦听器。然后我将linkClicked 从 TheSidebar.vue 发送到 App.vue,App.vue 通过this.$router.push(path) 将路径推送到我的路由器。不过,这似乎会导致我的历史状态出现问题。非常感谢!

以上是关于在Vue 3中单击路由器链接时如何在不重新加载页面的情况下切换侧边栏的主要内容,如果未能解决你的问题,请参考以下文章

在不使用 UpdatePanel 的情况下单击 RadioButtonList 时如何停止页面重新加载?

React 不会在路由参数更改或查询更改时重新加载组件数据

如何在不重新加载 vue.js 页面的情况下翻译应用程序?

来自服务器数据的 Vue2 路由器链接

Backbone.js 在不重新加载页面的情况下更改 url

Next.js-express 动态路由导致页面重新加载