Vue-Router

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

介绍Vue Router

Vue Router是Vue.js的官方路由。它与Vue.js核心深度集成让用
Vue.js构建单页应用变得轻而易举。功能包括

  • 嵌套路由映射
  • 动态路由选择
  • 模块化、基于组件的路由配置
  • 路由参数、查询、通配符
  • 展示由Vue.js的过渡系统提供的过渡效果
  • 细致的导航控制
  • 自动激活CSS类的链接
  • HTML5 history 模式或 hash模式
  • 可定制的滚动行为
  • URL的正确编码

安装

直接下载

http://unpkg.com/vue-router@4

Unpkg.com提供了基于npm的CDN链接。上述链接将指向npm上的最新版本。你也可以像http://unpkg.com/vue-router@3.0.0/dist/vue-router.js这样的URL来使用特定的版本或Tag。

npm

npm install vue-router@4

yarn

yarn add vue-router@4

路由的核心改变URL但是页面不进行整体刷新
路由理解为指向。

路由表是一个映射表一个路由就是一组映射关系key:value,key:表示路由value:可以为function 或者component

入门

用Vue+Vue Router创建单页应用非常简单通过Vue.js我们已经用组件组成了我们的应用。当加入Vue Router时我们需要做的就是将我们的组件映射到路由器上让Vue Router知道在哪里渲染它们。

HTML

<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!--使用 router-link 组件进行导航 -->
    <!--通过传递 `to` 来指定链接 -->
    <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
    <router-link to="/">Go to Home</router-link>
    <router-link to="/about">Go to About</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>

router-link

请注意我们没有使用常规的a标签而是使用一个自定义组件 router-link来创建链接。这使得Vue Router可以在不重新加载页面的情况下更改URL处理URL的生成以及编码。

router-view

router-view 将显示与URL对应的组件。你可以把它放在任何地方以适应你的布局。

JavaScript

// 1. 定义路由组件.
// 也可以从其他文件导入
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }

// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({
  // 4. 内部提供了 history 模式的实现。为了简单起见我们在这里使用 hash 模式。
  history: VueRouter.createWebHashHistory(),
  routes, // `routes: routes` 的缩写
})

// 5. 创建并挂载根实例
const app = Vue.createApp({})
//确保 _use_ 路由实例使
//整个应用支持路由。
app.use(router)

app.mount('#app')

// 现在应用已经启动了

带参数的动态路由匹配

很多时候我们需要将给定匹配模式的路由映射到同一个组件。例如我们可能有一个user组件它应该对所有用户进行渲染但用户ID不同。在Vue Router中我们可以在路径中使用一个动态字段来实现我们称之为路径参数。

const User = {
  template: '<div>User</div>',
}

// 这些都会传递给 `createRouter`
const routes = [
  // 动态字段以冒号开始
  { path: '/users/:id', component: User },
]

现在像/users/johnny 和 /users/jolyne 这样的URL都会映射到同一个路由

路径参数用冒号 : 表示。当一个路由被匹配时它的params的值将在每个组件中以 this.$route.params 的形式暴露出来。

响应路由参数的变化

使用带有参数的路由时需要注意的是当用户从 /users/johnny 导航到 /users/jolyne 时相同的组件实例将被重复使用。因为两个路由都渲染同个组件比起销毁再创建复用则显得更加高效。不过这也意味着组件的生命周期钩子不会被调用。

捕获所有路由或404 Not found 路由

常规参数只匹配URL片段之间的字符用/分隔。如果我们想匹配任意路径我们可以使用自定义的路径参数正则表达式在路径参数后面的括号中加入正则表达式

const routes = [
  // 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
  // 将匹配以 `/user-` 开头的所有内容并将其放在 `$route.params.afterUser` 下
  { path: '/user-:afterUser(.*)', component: UserGeneric },
]

在这个特定的场景中我们在括号之间使用了自定义正则表达式并将pathMath参数标记为可选可重复。这样做是为了让我们在需要的时候可以通过将path拆分成一个数组直接导航到路由。

this.$router.push({
  name: 'NotFound',
  // 保留当前路径并删除第一个字符以避免目标 URL 以 `//` 开头。
  params: { pathMatch: this.$route.path.substring(1).split('/') },
  // 保留现有的查询和 hash 值如果有的话
  query: this.$route.query,
  hash: this.$route.hash,
})

路由的匹配语法

大多数应用都会使用/about这样的静态路由和/users/:userId这样的动态路由就像我们刚才在动态路由匹配中看到的那样但是Vue Router可以提供更多的方式

TIP

为了简单起见所有的路由都省略了component属性只关注path值

在参数中自定义正则

当定义像 :userId 这样的参数时我们内部使用以下的正则 ([^/]+) (至少有一个字符不是斜杠 / )来从 URL 中提取参数。这很好用除非你需要根据参数的内容来区分两个路由。想象一下两个路由 /:orderId 和 /:productName两者会匹配完全相同的 URL所以我们需要一种方法来区分它们。最简单的方法就是在路径中添加一个静态部分来区分它们

const routes = [
  // 匹配 /o/3549
  { path: '/o/:orderId' },
  // 匹配 /p/books
  { path: '/p/:productName' },
]

但在某些情况下我们并不想添加静态的 /o /p 部分。由于orderId 总是一个数字而 productName 可以是任何东西所以我们可以在括号中为参数指定一个自定义的正则

const routes = [
  // /:orderId -> 仅匹配数字
  { path: '/:orderId(\\d+)' },
  // /:productName -> 匹配其他任何内容
  { path: '/:productName' },
]

Tip

确保转义反斜杠( \ )就像我们对 \d (变成\d)所做的那样在 JavaScript 中实际传递字符串中的反斜杠字符。

可重复的参数

如果你需要匹配具有多个部分的路由如 /first/second/third你应该用 *0 个或多个和 +1 个或多个将参数标记为可重复

const routes = [
  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  { path: '/:chapters+' },
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  { path: '/:chapters*' },
]

嵌套路由

一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下URL 的片段通常对应于特定的嵌套组件结构例如

/user/johnny/profile                     /user/johnny/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

通过 Vue Router你可以使用嵌套路由配置来表达这种关系。

注意以 / 开头的嵌套路径将被视为根路径。这允许你利用组件嵌套而不必使用嵌套的 URL。

children 配置只是另一个路由数组就像 routes 本身一样。因此你可以根据自己的需要不断地嵌套视图。

嵌套的命名路由

在处理命名路由时你通常会给子路由命名

const routes = [
  {
    path: '/user/:id',
    component: User,
    // 请注意只有子路由具有名称
    children: [{ path: '', name: 'user', component: UserHome }],
  },
]

这将确保导航到 /user/:id 时始终显示嵌套路由。

在一些场景中你可能希望导航到命名路由而不导航到嵌套路由。例如你想导航 /user/:id 而不显示嵌套路由。那样的话你还可以命名父路由但请注意重新加载页面将始终显示嵌套的子路由因为它被视为指向路径/users/:id 的导航而不是命名路由

const routes = [
  {
    path: '/user/:id',
    name: 'user-parent'
    component: User,
    children: [{ path: '', name: 'user', component: UserHome }],
  },
]

编程式导航

除了使用 创建 a 标签来定义导航链接我们还可以借助 router 的实例方法通过编写代码来实现。

导航到不同的位置

注意在 Vue 实例中你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。

想要导航到不同的 URL可以使用 router.push 方法。这个方法会向 history 栈添加一个新的记录所以当用户点击浏览器后退按钮时会回到之前的 URL。

当你点击 时内部会调用这个方法所以点击 相当于调用 router.push(…)

声明式编程式
<router-link :to=“…”>router.push(…)

该方法的参数可以是一个字符串路径或者一个描述地址的对象

// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由并加上参数让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash结果是 /about#team
router.push({ path: '/about', hash: '#team' })

注意如果提供了 pathparams 会被忽略上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法你需要提供路由的 name 或手写完整的带有参数的 path

const username = 'eduardo'
// 我们可以手动建立 url但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user

当指定 params 时可提供 string 或 number 参数或者对于可重复的参数可提供一个数组。任何其他类型如 undefined、false 等都将被自动字符串化。 对于可选参数你可以提供一个空字符串“”来跳过它。

由于属性 to 与 router.push 接受的对象种类相同所以两者的规则完全相同。

替换当前位置

它的作用类似于 router.push唯一不同的是它在导航时不会向 history 添加新记录正如它的名字所暗示的那样——它取代了当前的条目。

声明式编程式
<router-link :to=“…” replace>router.replace(…)

横跨历史

该方法采用一个整数作为参数表示在历史堆栈中前进或后退多少步类似于 window.history.go(n)。

// 向前移动一条记录与 router.forward() 相同
router.go(1)

// 返回一条记录与 router.back() 相同
router.go(-1)

// 前进 3 条记录
router.go(3)

// 如果没有那么多记录静默失败
router.go(-100)
router.go(100)

篡改历史

你可能已经注意到router.push、router.replace 和 router.go 是 window.history.pushState、window.history.replaceState 和 window.history.go 的翻版它们确实模仿了 window.history 的 API。

因此如果你已经熟悉 Browser History APIs在使用 Vue Router 时操作历史记录就会觉得很熟悉。

值得一提的是无论在创建路由器实例时传递什么样的 history 配置Vue Router 的导航方法( push、replace、go )都能始终正常工作。

命名路由

除了 path 之外你还可以为任何路由提供 name。这有以下优点

  • 没有硬编码的 URL
  • params 的自动编码/解码。
  • 防止你在 url 中出现打字错误。
  • 绕过路径排序如显示一个
const routes = [
  {
    path: '/user/:username',
    name: 'user',
    component: User,
  },
]

要链接到一个命名的路由可以向 router-link 组件的 to 属性传递一个对象

<router-link :to="{ name: 'user', params: { username: 'erina' }}">
  User
</router-link>

这跟代码调用 router.push() 是一回事

router.push({ name: 'user', params: { username: 'erina' } })

在这两种情况下路由将导航到路径 /user/erina。

命名视图

有时候想同时 (同级) 展示多个视图而不是嵌套展示例如创建一个布局有 sidebar (侧导航) 和 main (主内容) 两个视图这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图而不是只有一个单独的出口。如果 router-view 没有设置名字那么默认为 default。

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

一个视图使用一个组件渲染因此对于同个路由多个视图就需要多个组件。确保正确使用 components 配置 (带上 s)

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})

嵌套命名视图

我们也有可能使用命名视图创建嵌套视图的复杂布局。这时你也需要命名用到的嵌套 router-view 组件。我们以一个设置面板为例

/settings/emails                                       /settings/profile
+-----------------------------------+                  +------------------------------+
| UserSettings                      |                  | UserSettings                 |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | |  +------------>  | | Nav | UserProfile        | |
| |     +-------------------------+ |                  | |     +--------------------+ |
| |     |                         | |                  | |     | UserProfilePreview | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+

Nav 只是一个常规组件。
UserSettings 是一个视图组件。
UserEmailsSubscriptions、UserProfile、UserProfilePreview 是嵌套的视图组件。
注意我们先忘记 HTML/CSS 具体的布局的样子只专注在用到的组件上。

UserSettings 组件的 部分应该是类似下面的这段代码:

<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar />
  <router-view />
  <router-view name="helper" />
</div>

那么你就可以通过这个路由配置来实现上面的布局

{
  path: '/settings',
  // 你也可以在顶级路由就配置命名视图
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}

重定向和别名

重定向

重定向也是通过 routes 配置来完成下面例子是从 /home 重定向到 /

const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由

const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法动态返回重定向目标

const routes = [
  {
    // /search/screens -> /search?q=screens
    path: '/search/:searchText',
    redirect: to => {
      // 方法接收目标路由作为参数
      // return 重定向的字符串路径/路径对象
      return { path: '/search', query: { q: to.params.searchText } }
    },
  },
  {
    path: '/search',
    // ...
  },
]

请注意导航守卫并没有应用在跳转路由上而仅仅应用在其目标上。在上面的例子中在 /home 路由中添加 beforeEnter 守卫不会有任何效果。

在写 redirect 的时候可以省略 component 配置因为它从来没有被直接访问过所以没有组件要渲染。唯一的例外是嵌套路由如果一个路由记录有 children 和 redirect 属性它也应该有 component 属性。

相对重定向

也可以重定向到相对位置

const routes = [
  {
    // 将总是把/users/123/posts重定向到/users/123/profile。
    path: '/users/:id/posts',
    redirect: to => {
      // 该函数接收目标路由作为参数
      // 相对位置不以`/`开头
      // 或 { path: 'profile'}
      return 'profile'
    },
  },
]

别名

重定向是指当用户访问 /home 时URL 会被 / 替换然后匹配成 /。那么什么是别名呢

将 / 别名为 /home意味着当用户访问 /home 时URL 仍然是 /home但会被匹配为用户正在访问 /。

上面对应的路由配置为

const routes = [{ path: '/', component: Homepage, alias: '/home' }]

通过别名你可以自由地将 UI 结构映射到一个任意的 URL而不受配置的嵌套结构的限制。使别名以 / 开头以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来用一个数组提供多个别名

const routes = [
  {
    path: '/users',
    component: UsersLayout,
    children: [
      // 为这 3 个 URL 呈现 UserList
      // - /users
      // - /users/list
      // - /people
      { path: '', component: UserList, alias: ['/people', 'list'] },
    ],
  },
]

如果你的路由有参数请确保在任何绝对别名中包含它们

const routes = [
  {
    path: '/users/:id',
    component: UsersByIdLayout,
    children: [
      // 为这 3 个 URL 呈现 UserDetails
      // - /users/24
      // - /users/24/profile
      // - /24
      { path: 'profile', component: UserDetails, alias: ['/:id', ''] },
    ],
  },
]

关于 SEO 的注意事项: 使用别名时一定要定义规范链接.

将props传递给路由组件

在你的组件中使用 $route 会与路由紧密耦合这限制了组件的灵活性因为它只能用于特定的 URL。虽然这不一定是件坏事但我们可以通过 props 配置来解除这种行为

我们可以将下面的代码

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const routes = [{ path: '/user/:id', component: User }]

替换成

const User = {
  // 请确保添加一个与路由参数完全相同的 prop 名
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]

这允许你在任何地方使用该组件使得该组件更容易重用和测试。

布尔模式

当 props 设置为 true 时route.params 将被设置为组件的 props。

命名视图

对于有命名视图的路由你必须为每个命名视图定义 props 配置

const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false }
  }
]

对象模式

当 props 是一个对象时它将原样设置为组件 props。当 props 是静态的时候很有用。

const routes = [
  {
    path: '/promotion/from-newsletter',
    component: Promotion,
    props: { newsletterPopup: false }
  }
]

函数模式

你可以创建一个返回 props 的函数。这允许你将参数转换为其他类型将静态值与基于路由的值相结合等等。

const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }
]

URL /search?q=vue 将传递 {query: ‘vue’} 作为 props 传给 SearchUser 组件。

请尽可能保持 props 函数为无状态的因为它只会在路由发生变化时起作用。如果你需要状态来定义 props请使用包装组件这样 vue 才可以对状态变化做出反应。

不同的历史模式

在创建路由器实例时history 配置允许我们在不同的历史模式中进行选择。

Hash 模式

hash 模式是用 createWebHashHistory() 创建的

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

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    //...
  ],
})

它在内部传递的实际 URL 之前使用了一个哈希字符#。由于这部分 URL 从未被发送到服务器所以它不需要在服务器层面上进行任何特殊处理。不过它在 SEO 中确实有不好的影响。如果你担心这个问题可以使用 HTML5 模式。

HTML5 模式

用 createWebHistory() 创建 HTML5 模式推荐使用这个模式

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

const router = createRouter({
  history: createWebHistory(),
  routes: [
    //...
  ],
})

当使用这种历史模式时URL 会看起来很 “正常”例如 https://example.com/user/id。漂亮!

不过问题来了。由于我们的应用是一个单页的客户端应用如果没有适当的服务器配置用户在浏览器中直接访问 https://example.com/user/id就会得到一个 404 错误。这就尴尬了。

不用担心要解决这个问题你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧!

服务器配置示例#
注意以下示例假定你正在从根目录提供服务。如果你部署到子目录你应该使用Vue CLI 的 publicPath 配置和相关的路由器的 base 属性。你还需要调整下面的例子以使用子目录而不是根目录例如将RewriteBase/ 替换为 RewriteBase/name-of-your-subfolder/。

Apache

<IfModule mod_negotiation.c>
  Options -MultiViews
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

也可以使用 FallbackResource 代替 mod_rewrite。

nginx

location / {
  try_files $uri $uri/ /index.html;
}

原生 Node.js

const http = require('http')
const fs = require('fs')
const httpPort = 80

http
  .createServer((req, res) => {
    fs.readFile('index.html', 'utf-8', (err, content) => {
      if (err) {
        console.log('We cannot open "index.html" file.')
      }

      res.writeHead(200, {
        'Content-Type': 'text/html; charset=utf-8',
      })

      res.end(content)
    })
  })
  .listen(httpPort, () => {
    console.log('Server listening on: http://localhost:%s', httpPort)
  })

Express + Node.js

对于 Node.js/Express可以考虑使用 connect-history-api-fallback 中间件。

Internet Information Services (IIS)#
安装 IIS UrlRewrite
在网站的根目录下创建一个 web.config 文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Handle History Mode and custom 404/500" stopProcessing="true">
          <match url="(.*)" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Caddy v2

try_files {path} /
Caddy v1#
rewrite {
    regexp .*
    to {path} /
}

Firebase hosting

将此添加到你的 firebase.json 中

{
  "hosting": {
    "public": "dist",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

Netlify

创建一个 _redirects 文件包含在你的部署文件中

/* /index.html 200
在 vue-cli、nuxt 和 vite 项目中这个文件通常放在名为 static 或 public 的目录下。

你可以在 Netlify 文档中找到更多关于语法的信息。你也可以创建一个 netlify.toml 来结合其他 Netlify 功能的重定向。

Vercel

在项目根目录创建一个vercel.json文件内容如下

{
  "rewrites": [{ "source": "/:path*", "destination": "/index.html" }]
}

Caveat

这有一个注意事项。你的服务器将不再报告 404 错误因为现在所有未找到的路径都会显示你的 index.html 文件。为了解决这个问题你应该在你的 Vue 应用程序中实现一个万能的路由来显示 404 页面。

const router = createRouter({
  history: createWebHistory(),
  routes: [{ path: '/:pathMatch(.*)', component: NotFoundComponent }],
})

另外如果你使用的是 Node.js 服务器你可以通过在服务器端使用路由器来匹配传入的 URL如果没有匹配到路由则用 404 来响应从而实现回退。

导航守卫

全局前置守卫

你可以使用 router.beforeEach 注册一个全局前置守卫

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

当一个导航触发时全局前置守卫按照创建顺序调用。守卫是异步解析执行此时导航在所有守卫 resolve 完之前一直处于等待中。

每个守卫方法接收两个参数

  • to: 即将要进入的目标 用一种标准化的方式
  • from: 当前导航正要离开的路由 用一种标准化的方式

可以返回的值如下:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮)那么 URL 地址会重置到 from路由对应的地址。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址就像你调用 router.push() 一样你可以设置诸如replace: true 或 name: ‘home’ 之类的配置。当前的导航被中断然后进行一个新的导航就和 from 一样。
router.beforeEach(async (to, from) => {
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
     // 将用户重定向到登录页面
     return { name: 'Login' }
   }
 })

如果遇到了意料之外的情况可能会抛出一个 Error。这会取消导航并且调用 router.onError() 注册过的回调。

如果什么都没有undefined 或返回 true则导航是有效的并调用下一个导航守卫

以上所有都同 async 函数 和 Promise 工作方式一样

router.beforeEach(async (to, from) => {
  // canUserAccess() 返回 `true` 或 `false`
  const canAccess = await canUserAccess(to)
  if (!canAccess) return '/login'
})

可选的第三个参数 next

在之前的 Vue Router 版本中也是可以使用 第三个参数 next 的。这是一个常见的错误来源可以通过 RFC 来消除错误。然而它仍然是被支持的这意味着你可以向任何导航守卫传递第三个参数。在这种情况下确保 next 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次但是只能在所有的逻辑路径都不重叠的情况下否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到/login的错误用例

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份则 `next` 会被调用两次
  next()
})

下面是正确的版本:

// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

组件内的守卫

最后你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)

可用的配置 API

你可以为路由组件添加以下配置

beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` 
    // 因为当守卫执行时组件实例还没被创建
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变但是该组件被复用时调用
    // 举例来说对于一个带有动态参数的路径 `/users/:id`在 `/users/1` 和 `/users/2` 之间跳转的时候
    // 由于会渲染同样的 `UserDetails` 组件因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候组件已经挂载好了导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样它可以访问组件实例 `this`
  },
}

beforeRouteEnter 守卫 不能 访问 this因为守卫在导航确认前被调用因此即将登场的新组件还没被创建。

不过你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调并且把组件实例作为回调方法的参数

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说this 已经可用了所以不支持 传递回调因为没有必要了

beforeRouteUpdate (to, from) {
  // just use `this`
  this.name = to.params.name
}

这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。

beforeRouteLeave (to, from) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (!answer) return false
}

路由懒加载

当打包构建应用时JavaScript 包会变得非常大影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块然后当路由被访问的时候才加载对应组件这样就会更加高效。

Vue Router 支持开箱即用的动态导入这意味着你可以用动态导入代替静态导入

// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})

component (和 components) 配置接收一个返回 Promise 组件的函数Vue Router 只会在第一次进入页面时才会获取这个函数然后使用缓存数据。这意味着你也可以使用更复杂的函数只要它们返回一个 Promise

const UserDetails = () =>
  Promise.resolve({
    /* 组件定义 */
  })

一般来说对所有的路由都使用动态导入是个好主意。

注意

不要在路由中使用异步组件。异步组件仍然可以在路由组件中使用但路由组件本身就是动态导入的。

如果你使用的是 webpack 之类的打包器它将自动从代码分割中受益。

如果你使用的是 Babel你将需要添加 syntax-dynamic-import 插件才能使 Babel 正确地解析语法。

把组件按组分块#
使用 webpack#
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用命名 chunk一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)

const UserDetails = () =>
  import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
  import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
  import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')

webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。

使用 Vite

在Vite中你可以在rollupOptions下定义分块

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      // https://rollupjs.org/guide/en/#outputmanualchunks
      output: {
        manualChunks: {
          'group-user': [
            './src/UserDetails',
            './src/UserDashboard',
            './src/UserProfileEdit',
          ],
        },
    },
  },
})

index.js

// 1. 定义路由组件.
// 也可以从其他文件导入
import {createRouter,createWebHashHistory,createWebHistory} from "vue-router";
// 静态加载
// import Home from "../views/home.vue";
import About from "../views/about.vue";
import User from "../views/User.vue";
import NotFound from "../views/NotFound.vue";
import News from "../views/News.vue";
import Parent from "../views/Parent.vue";
import StyleOne from "../views/styleOne.vue";
import StyleTwo from "../views/styleTwo.vue";
import Page from "../views/Page.vue";
import ShopTop from "../views/ShopTop.vue";
import ShopMain from "../views/ShopMain.vue";
import ShopBottom from "../views/ShopBottom.vue";


// 路由懒加载用到时再加载
const Home = () =>import('../views/home.vue')

// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [
    { path: '/',
      //   重定向
      // redirect:"/home"
      //   命名路由
      //  redirect:{name:'home'}
    //     方法
        redirect:(to)=>{
            console.log(to);
            return {path:'/home'}
        }
    },
    { name:'home' , path: '/home', component: Home},
    { path: '/about', component: About ,
        // 每路守卫路由独享的守卫
        beforeEnter:(to,from,next)=>{
            console.log(to);
            console.log(from);
            if (123 === 123)
                next();
        }
    },
    { path: '/user/:id' , component: User , props:true},
    // 使用正则的方式匹配任意的
    { path: '/:path(.*)' , component: NotFound},
    // 数字
    // { path: '/news/:id(\\d+)' , component: News },
    // // 多个参数
    // { path: '/news/:id+' , component: News },
    // 参数可有可无
    { name:"news" , path: '/news/:id*' , component: News },
    // 参数可有可无可是参数不可以重复叠加
    // { path: '/news/:id?' , component: News },

    { path: '/parent' ,
      alias:['/father' ,'/fuqin'],//别名
      component: Parent,
      children:[
          {path: 'styleone' , component:StyleOne}  ,
          {path: 'styletwo' , component: StyleTwo}
      ]},

    { path: '/page' , component: Page},
    { path: '/shop/:id' ,
        components: {
            default:ShopMain,
                ShopTop,ShopBottom
        },
        props:{
            default:true,
            ShopTop:true,
            // ShopTop:false ,
            ShopBottom:false,
        }
    }
]

// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置但我们在这里
// 暂时保持简单
const router = createRouter({
    // 4. 内部提供了 history 模式的实现。为了简单起见我们在这里使用 hash 模式。
    // history:createWebHashHistory(),
    // history模式,二者区别有无#
    history:createWebHistory(),
    routes, // `routes: routes` 的缩写
})

// 全局守卫
router.beforeEach((to,from,next)=>{
    console.log(to);
    console.log(from);
    next() //通行证
})
export default router

App.vue

<script setup>

</script>

<template>
<!--  vue-router是基于路由和组件的路由是用来设定访问路径将路径和组件映射起来-->
  <div id="app">
    <h1>Hello App!</h1>
    <p>
      <!--使用 router-link 组件进行导航 -->
      <!--通过传递 `to` 来指定链接 -->
      <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
      <router-link to="/">Go to Home</router-link>
      <router-link to="/about">Go to About</router-link>
      <router-link to="/user/123">Go to User</router-link>
<!--      <router-link to="/news/456">Go to News</router-link>-->
      <router-link :to="{name:'news' , params:{id:456}}">Go to News</router-link>
      <router-link to="/parent">Go to Parent</router-link>
      <router-link to="/page">Go to Page</router-link>
    </p>
    <!-- 路由出口 -->
    <!-- 路由匹配到的组件将渲染在这里 -->
    <router-view name="ShopTop"></router-view>
    <router-view></router-view>
    <router-view name="ShopBottom"></router-view>
  </div>

</template>

<style>

</style>

Home.vue

<template>
  <div>home</div>
</template>

About.vue

<template>
  <div>about</div>
  <button @click="goBack">后退</button>
</template>

<script>
export default {
  mounted(){
    console.log(this.$route)
  },
  methods:{
    goBack(){
      // 前进传入正值后退传入负值
      // this.$router.go(-1)
      this.$router.back()  //相当于go(-1)
      this.$router.forword()  //相当于go(1)
    }
  }
}
</script>

News.vue

<template>
  新闻
</template>
<script>
export default {
  data(){
    return {
      age:18
    }
  },
  beforeRouteEnter(to,from,next){
    console.log("路由进入组件之前")
    console.log(to);
    console.log(from)
    next((vm)=>{
      console.log(vm.age)
    })
  },
  beforeRouteUpdate(){
    console.log("路由更新组件之前")
  },
  beforeRouteLeave(){
    console.log("路由离开组件之前")
  },
}
</script>

NotFound.vue

<template>
  <div>
    找不到当前页面
  </div>
</template>

Page.vue

<template>
  <div>
    <p>Page页面</p>
    <button @click="goPage">跳转页面</button>
  </div>
</template>

<script>
export default {
  methods:{
    goPage:function (){
      // this.$router.push('/')
      // 通过传入对象
      // this.$router.push({path:'/'})
    //   带参数
    //   this.$router.push({path:'/user/123'})
    //   this.$router.push({name:"news" , params:{id:"123456"}})
      this.$router.push({path:"/about" , query:{name:"张三"}})
    //   替换当前界面
    //   this.$router.push({path:"/about" , query:{name:"张三"} , replace:true})
    //   this.$router.replace({path:"/about" , query:{name:"张三"}})
    }
  }
}
</script>

Parent.vue

<template>
  <div>
    <h2>父组件Parent</h2>
    <router-link to="/parent/styleone">样式一</router-link>
    <router-link to="/parent/styletwo">样式二</router-link>
    <router-view></router-view>
  </div>
</template>

ShopBottom.vue

<template>
  <div>
    <h2>Shop底部</h2>
  </div>
</template>

ShopMain.vue

<template>
  <div>
    <h2>Shop主要内容</h2>
  </div>
</template>

<script>
export default {
  props:['id'],
  mounted() {
    console.log(this.id)
  }
}
</script>

ShopTop.vue

<template>
  <div>
    <h2>Shop头部</h2>
  </div>
</template>

<script>
export default {
  props:['id'],
  mounted() {
    console.log(this.id)
  }
}
</script>

StyleOne.vue

<template>
  <div>
    <p>样式一</p>
    <ol>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
    </ol>
  </div>

</template>

StyleTwo.vue

<template>
  <div>
    <p>样式二</p>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
    </ul>
  </div>
</template>

User.vue

<template>
  <div @click="changeid">用户</div>
</template>

<!--<script>-->
<!--export default {-->
<!--  props:['id'],-->
<!--  mounted() {-->
<!--    // this.$route表示当前活跃的路由对象-->
<!--    console.log(this.$route.params.id);-->
<!--    console.log(this.id);-->
<!--  },-->
<!--}-->

<!--  methods:{-->
<!--    changeid(){-->
<!--      console.log(this.$route.params.id)-->
<!--    }-->
<!--  }-->
<!--}-->
<!--</script>-->

<script setup>
import {useRoute} from "vue-router";
console.log(useRoute().params.id)
const props = defineProps({
  id:String
})
console.log(props.id)
</script>
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: vue