一、路由

服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时,浏览器会从服务端获得全新的 HTML,然后重新加载整个页面。

然而,在单页面应用中,客户端的 JavaScript 可以拦截页面的跳转请求,动态获取新的数据,然后在无需重新加载的情况下更新当前页面。这样通常可以带来更顺滑的用户体验,尤其是在更偏向“应用”的场景下,因为这类场景下用户通常会在很长的一段时间中做出多次交互。

在这类单页应用中,“路由”是在客户端执行的。一个客户端路由器的职责就是利用诸如 History API 或是 hashchange 事件这样的浏览器 API 来管理应用当前应该渲染的视图。

二、从头开始实现一个简单的路由

如果你只需要一个简单的页面路由,而不想为此引入一整个路由库,你可以通过动态组件的方式,监听浏览器 hashchange 事件或使用 History API 来更新当前组件。

下面是一个简单的例子:

<script setup>
import { ref, computed } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import NotFound from './NotFound.vue'
const routes = {
  '/': Home,
  '/about': About
}
const currentPath = ref(window.location.hash)
window.addEventListener('hashchange', () => {
  currentPath.value = window.location.hash
})
const currentView = computed(() => {
  return routes[currentPath.value.slice(1) || '/'] || NotFound
})
</script>
<template>
  <a href="#/">Home</a> |
  <a href="#/about">About</a> |
  <a href="#/non-existent-path">Broken Link</a>
  <component :is="currentView" />
</template>

三、Vue Router

Vue Router 是 Vue 官方的客户端路由解决方案。

客户端路由的作用是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载。

Vue Router 基于 Vue 的组件系统构建,你可以通过配置路由来告诉 Vue Router 为每个 URL 路径显示哪些组件。

官网:https://router.vuejs.org/zh/guide/

四、Vue Router示例

4.1 App.vue

<template>
  <h1>Hello App!</h1>
  <p>
    <strong>Current route path:</strong> {{ $route.fullPath }}
  </p>
  <nav>
    <RouterLink to="/">Go to Home</RouterLink>
    <RouterLink to="/about">Go to About</RouterLink>
  </nav>
  <main>
    <RouterView />
  </main>
</template>

在这个 template 中使用了两个由 Vue Router 提供的组件: RouterLink 和 RouterView。

不同于常规的<a>标签,我们使用组件 RouterLink 来创建链接。这使得 Vue Router 能够在不重新加载页面的情况下改变 URL,处理 URL 的生成、编码和其他功能。我们将会在之后的部分深入了解 RouterLink 组件。

RouterView 组件可以使 Vue Router 知道你想要在哪里渲染当前 URL 路径对应的路由组件。它不一定要在 App.vue 中,你可以把它放在任何地方,但它需要在某处被导入,否则 Vue Router 就不会渲染任何东西。

上述示例还使用了{{ $route.fullPath }}。你可以在组件模板中使用 $route 来访问当前的路由对象。

4.2 创建路由器实例

路由器实例是通过调用 createRouter() 函数创建的:

import { createMemoryHistory, createRouter } from 'vue-router'

import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'

const routes = [
  { path: '/', component: HomeView },
  { path: '/about', component: AboutView },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})

这里的 routes 选项定义了一组路由,把 URL 路径映射到组件。其中,由 component 参数指定的组件就是先前在 App.vue 中被 渲染的组件。这些路由组件通常被称为视图,但本质上它们只是普通的 Vue 组件。

其他可以设置的路由选项我们会在之后介绍,目前我们只需要 path 和 component。

这里的 history 选项控制了路由和 URL 路径是如何双向映射的。在演练场的示例里,我们使用了 createMemoryHistory(),它会完全忽略浏览器的 URL 而使用其自己内部的 URL。 这在演练场中可以正常工作,但是未必是你想要在实际应用中使用的。通常,你应该使用 createWebHistory() 或 createWebHashHistory()。我们将在不同的历史记录模式的部分详细介绍这个主题。

4.3 注册路由器插件

一旦创建了我们的路由器实例,我们就需要将其注册为插件,这一步骤可以通过调用 use() 来完成。

createApp(App)
  .use(router)
  .mount('#app')

或等价地:

const app = createApp(App)
app.use(router)
app.mount('#app')

和大多数的 Vue 插件一样,use() 需要在 mount() 之前调用。

如果你好奇这个插件做了什么,它的职责包括:

  • 全局注册 RouterView 和 RouterLink 组件。
  • 添加全局 $router 和 $route 属性。
  • 启用 useRouter() 和 useRoute() 组合式函数。
  • 触发路由器解析初始路由。

4.4 访问路由器和当前路由

你很可能想要在应用的其他地方访问路由器。

如果你是从 ES 模块导出路由器实例的,你可以将路由器实例直接导入到你需要它的地方。在一些情况下这是最好的方法,但如果我们在组件内部,那么我们还有其他选择。

在组件模板中,路由器实例将被暴露为 $router。这与同样被暴露的 $route 一样,但注意前者最后有一个额外的 r。

如果我们使用选项式 API,我们可以在 JavaScript 中如下访问这两个属性:this.$router 和 this.$route。在演练场示例中的 HomeView.vue 组件中,路由器就是这样获取的。

export default {
  methods: {
    goToAbout() {
      this.$router.push('/about')
    },
  },
}

对于组合式 API,我们不能通过 this 访问组件实例,所以 Vue Router 给我们提供了一些组合式函数。

<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const router = useRouter()
const route = useRoute()

const search = computed({
  get() {
    return route.query.search ?? ''
  },
  set(search) {
    router.replace({ query: { search } })
  }
})
</script>