在 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.vue
到 TheSidebar.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"
如果有人能阐明这一点,那就太好了,因为我对这里发生的事情一头雾水。
我有一个类似于模式的边栏组件。单击按钮时,侧边栏会转换为带有导航 links 的视口。这些导航 links 实际上是连接到 vue-router 的 router-links。
我想要完成的事情
当我单击侧边栏组件内的路由器-link 时,我希望侧边栏从视口过渡并且我希望单击的路由器-link 的组件呈现无需重新加载页面。
当前情况
当我点击 router-link 时,侧边栏立即从 DOM 中删除。它不会按预期翻译出屏幕。此外,页面已重新加载。
我还尝试了什么
我还尝试将 <transition>
包装器连同关联的 CSS 类 一起移动到 TheSidebar.vue
组件中,然后我将 sidebarIsVisible
作为道具从App.vue
到 TheSidebar.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"
如果有人能阐明这一点,那就太好了,因为我对这里发生的事情一头雾水。