MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue Router 最佳实践与代码规范建议

2022-03-146.1k 阅读

1. Vue Router 基础概念回顾

在深入探讨最佳实践与代码规范之前,我们先来回顾一下 Vue Router 的基本概念。Vue Router 是 Vue.js 官方的路由管理器,它和 Vue.js 的核心深度集成,让我们可以方便地通过路由映射来构建单页应用(SPA)。

在 Vue Router 中,我们主要关注几个核心概念:路由、路由配置、路由导航。

路由:简单来说,就是 URL 与组件之间的映射关系。例如,当用户访问 /home 时,我们希望展示 Home 组件。

路由配置:通过一个数组来定义所有的路由映射关系。如下是一个简单的路由配置示例:

import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/components/Home';
import About from '@/components/About';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: About
    }
  ]
});

在上述代码中,我们定义了两个路由,根路径 '/' 映射到 Home 组件,'/about' 映射到 About 组件。

路由导航:指的是在应用中通过编程或者用户操作来切换路由的过程。例如,我们可以在模板中使用 <router - link> 组件来创建一个导航链接,也可以在 JavaScript 代码中使用 this.$router.push 方法来进行编程式导航。

<template>
  <div>
    <router - link to="/">Home</router - link>
    <router - link to="/about">About</router - link>
  </div>
</template>
// 编程式导航
this.$router.push('/about');

2. 路由配置的最佳实践

2.1 模块化路由配置

随着项目规模的增大,将所有路由配置放在一个文件中会变得难以维护。因此,我们推荐采用模块化的路由配置方式。

我们可以将不同功能模块的路由分开定义,然后在主路由文件中进行汇总。例如,假设我们有一个电商项目,有用户模块、商品模块和订单模块。

用户模块路由(userRoutes.js)

import Vue from 'vue';
import Router from 'vue-router';
import UserLogin from '@/components/user/UserLogin';
import UserRegister from '@/components/user/UserRegister';

Vue.use(Router);

export const userRoutes = [
  {
    path: '/user/login',
    name: 'UserLogin',
    component: UserLogin
  },
  {
    path: '/user/register',
    name: 'UserRegister',
    component: UserRegister
  }
];

商品模块路由(productRoutes.js)

import Vue from 'vue';
import Router from 'vue-router';
import ProductList from '@/components/product/ProductList';
import ProductDetail from '@/components/product/ProductDetail';

Vue.use(Router);

export const productRoutes = [
  {
    path: '/product/list',
    name: 'ProductList',
    component: ProductList
  },
  {
    path: '/product/detail/:id',
    name: 'ProductDetail',
    component: ProductDetail
  }
];

主路由文件(router.js)

import Vue from 'vue';
import Router from 'vue-router';
import { userRoutes } from './userRoutes';
import { productRoutes } from './productRoutes';

Vue.use(Router);

export default new Router({
  routes: [
   ...userRoutes,
   ...productRoutes
  ]
});

这样做的好处是每个模块的路由配置清晰明了,便于开发和维护。当某个模块的路由需要修改时,我们可以直接定位到对应的路由文件。

2.2 动态路由参数

在实际应用中,我们经常需要通过动态参数来展示不同的数据。例如,在上面的商品详情路由 '/product/detail/:id' 中,:id 就是一个动态参数。

在组件中获取动态参数可以通过 $route.params。例如,在 ProductDetail 组件中:

export default {
  data() {
    return {
      productId: null
    };
  },
  created() {
    this.productId = this.$route.params.id;
    // 根据 productId 去获取商品详情数据
  }
};

需要注意的是,当路由参数变化时,例如从 /product/detail/1 切换到 /product/detail/2,组件实例不会被销毁,而是会复用。此时,如果我们需要根据新的参数重新获取数据,就需要监听 $route 的变化。

export default {
  data() {
    return {
      productId: null,
      productData: null
    };
  },
  created() {
    this.productId = this.$route.params.id;
    this.fetchProductData();
  },
  watch: {
    $route(to, from) {
      this.productId = to.params.id;
      this.fetchProductData();
    }
  },
  methods: {
    fetchProductData() {
      // 根据 productId 去获取商品详情数据
    }
  }
};

2.3 嵌套路由

很多时候,我们的页面结构是多层次的。例如,一个文章详情页面可能有文章内容、评论列表等子部分,这些子部分也可以作为独立的组件,通过嵌套路由来展示。

假设我们有一个博客应用,文章详情页面的路由配置如下:

import Vue from 'vue';
import Router from 'vue-router';
import ArticleDetail from '@/components/article/ArticleDetail';
import ArticleContent from '@/components/article/ArticleContent';
import ArticleComments from '@/components/article/ArticleComments';

Vue.use(Router);

export const articleRoutes = [
  {
    path: '/article/:id',
    name: 'ArticleDetail',
    component: ArticleDetail,
    children: [
      {
        path: '',
        name: 'ArticleContent',
        component: ArticleContent
      },
      {
        path: 'comments',
        name: 'ArticleComments',
        component: ArticleComments
      }
    ]
  }
];

ArticleDetail 组件的模板中,我们需要添加 <router - view> 来展示子路由对应的组件:

<template>
  <div>
    <router - view></router - view>
  </div>
</template>

当访问 /article/1 时,会展示 ArticleContent 组件;当访问 /article/1/comments 时,会展示 ArticleComments 组件。

3. 路由导航的最佳实践

3.1 使用 router - link 进行导航

<router - link> 是 Vue Router 提供的用于创建导航链接的组件。它会根据 to 属性的值生成对应的 HTML 链接,并自动处理激活状态。

例如,我们创建一个导航栏:

<template>
  <nav>
    <router - link to="/home" active - class="active">Home</router - link>
    <router - link to="/about" active - class="active">About</router - link>
  </nav>
</template>

<style>
.active {
  color: red;
}
</style>

在上述代码中,当用户访问 /home 时,Home 链接会添加 active 类,从而可以通过 CSS 样式来突出显示当前激活的导航项。

3.2 编程式导航的使用场景

虽然 <router - link> 很方便,但在某些情况下,我们需要使用编程式导航。例如,在用户登录成功后,自动跳转到首页。

export default {
  methods: {
    handleLogin() {
      // 登录逻辑
      if (this.isLoginSuccess) {
        this.$router.push('/home');
      }
    }
  }
};

另外,在一些复杂的业务逻辑中,例如根据用户权限决定导航到不同的页面,编程式导航也更加灵活。

export default {
  methods: {
    navigateBasedOnPermission() {
      if (this.hasAdminPermission) {
        this.$router.push('/admin/dashboard');
      } else {
        this.$router.push('/user/profile');
      }
    }
  }
};

3.3 导航守卫的合理运用

导航守卫是 Vue Router 提供的一种机制,用于在路由导航过程中进行一些拦截和处理。常见的导航守卫有全局前置守卫、全局后置钩子、路由独享守卫和组件内守卫。

全局前置守卫:可以用于验证用户是否登录。例如:

import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/components/Home';
import Login from '@/components/Login';

Vue.use(Router);

const router = new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    }
  ]
});

router.beforeEach((to, from, next) => {
  const isLoggedIn = localStorage.getItem('token');
  if (to.name!== 'Login' &&!isLoggedIn) {
    next({ name: 'Login' });
  } else {
    next();
  }
});

export default router;

在上述代码中,beforeEach 就是全局前置守卫。在每次路由导航前,它会检查用户是否登录(通过检查 localStorage 中的 token)。如果用户没有登录且访问的不是登录页面,就会自动跳转到登录页面。

路由独享守卫:可以在单个路由配置中定义。例如:

import Vue from 'vue';
import Router from 'vue-router';
import AdminDashboard from '@/components/AdminDashboard';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/admin/dashboard',
      name: 'AdminDashboard',
      component: AdminDashboard,
      beforeEnter: (to, from, next) => {
        const isAdmin = localStorage.getItem('role') === 'admin';
        if (isAdmin) {
          next();
        } else {
          next({ name: 'Home' });
        }
      }
    }
  ]
});

这里的 beforeEnter 就是路由独享守卫,只有当用户角色为 admin 时,才能访问 /admin/dashboard 页面。

4. Vue Router 的代码规范建议

4.1 路由命名规范

路由名称应该具有描述性,能够清晰地表达该路由对应的功能。例如,使用 UserLoginProductDetail 等有意义的名称,避免使用像 Page1Route2 这样无意义的命名。

同时,路由名称应该遵循驼峰命名法,这与 Vue 的命名风格保持一致。

4.2 路径命名规范

路由路径应该简洁明了,尽量使用小写字母和 - 分隔单词。例如,/user - login/product - list 等。避免使用大写字母和复杂的符号,这样既便于阅读,也符合 URL 的一般规范。

对于动态参数,使用 : 开头,并采用驼峰命名法,如 :productId

4.3 组件命名规范

与路由对应的组件命名也应该遵循一定的规范。通常采用帕斯卡命名法,即每个单词的首字母大写,例如 Home.vueAbout.vue。组件名称应该与路由名称有一定的关联,便于理解和维护。

4.4 代码结构规范

在路由配置文件中,应该保持清晰的结构。每个路由配置项应该有合理的缩进,并且注释要清晰。例如:

// 用户登录路由
{
  path: '/user/login',
  name: 'UserLogin',
  component: UserLogin
},
// 用户注册路由
{
  path: '/user/register',
  name: 'UserRegister',
  component: UserRegister
}

对于复杂的路由配置,如嵌套路由,可以适当增加注释来解释路由之间的关系。

4.5 导航守卫的规范

导航守卫的逻辑应该简洁明了,避免在守卫中编写过于复杂的业务逻辑。如果业务逻辑比较复杂,可以将其封装成独立的函数或模块,在守卫中调用。

同时,导航守卫的命名也应该具有描述性。例如,全局前置守卫中用于验证登录的函数可以命名为 checkLogin,在 beforeEach 中调用:

function checkLogin(to, from, next) {
  const isLoggedIn = localStorage.getItem('token');
  if (to.name!== 'Login' &&!isLoggedIn) {
    next({ name: 'Login' });
  } else {
    next();
  }
}

router.beforeEach(checkLogin);

5. 结合 Vuex 使用 Vue Router

在大型项目中,Vuex 状态管理库经常与 Vue Router 一起使用。例如,我们可以将用户登录状态存储在 Vuex 中,然后在路由导航守卫中根据 Vuex 中的状态来决定是否允许用户访问某些页面。

首先,在 Vuex 的 store.js 中定义用户登录状态:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    isLoggedIn: false
  },
  mutations: {
    setLoggedIn(state, value) {
      state.isLoggedIn = value;
    }
  },
  actions: {
    login({ commit }) {
      // 登录逻辑,登录成功后
      commit('setLoggedIn', true);
    }
  }
});

然后,在路由导航守卫中使用 Vuex 中的状态:

import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/components/Home';
import Login from '@/components/Login';
import store from './store';

Vue.use(Router);

const router = new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    }
  ]
});

router.beforeEach((to, from, next) => {
  const { isLoggedIn } = store.state;
  if (to.name!== 'Login' &&!isLoggedIn) {
    next({ name: 'Login' });
  } else {
    next();
  }
});

export default router;

这样,通过 Vuex 和 Vue Router 的结合,我们可以更好地管理应用的状态和路由导航,提高应用的可维护性和扩展性。

6. 处理路由切换时的动画效果

Vue Router 可以与 CSS 过渡或动画库结合,实现路由切换时的动画效果。

6.1 使用 CSS 过渡

我们可以利用 Vue 的 <transition> 组件来实现简单的 CSS 过渡效果。首先,在 App.vue 中包裹 <router - view>

<template>
  <div id="app">
    <transition name="fade">
      <router - view></router - view>
    </transition>
  </div>
</template>

<style>
.fade - enter - active,
.fade - leave - active {
  transition: opacity 0.5s;
}
.fade - enter,
.fade - leave - to {
  opacity: 0;
}
</style>

在上述代码中,我们定义了一个名为 fade 的过渡效果。当路由切换时,进入和离开的组件会有一个淡入淡出的动画效果。

6.2 使用动画库

如果需要更复杂的动画效果,可以使用第三方动画库,如 animate.css

首先,安装 animate.css

npm install animate.css

然后,在 main.js 中引入:

import Vue from 'vue';
import App from './App.vue';
import 'animate.css';

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount('#app');

接着,在 App.vue 中使用:

<template>
  <div id="app">
    <transition
      enter - class="animate__animated animate__fadeIn"
      leave - class="animate__animated animate__fadeOut"
    >
      <router - view></router - view>
    </transition>
  </div>
</template>

这样,路由切换时就会应用 animate.css 中的淡入淡出动画效果。通过合理运用动画效果,可以提升用户体验,使应用更加生动和吸引人。

7. 优化路由性能

7.1 路由懒加载

在大型项目中,路由对应的组件可能会比较大。如果在应用启动时就加载所有组件,会导致首屏加载时间过长。路由懒加载可以解决这个问题。

Vue Router 支持使用 ES6 的 import() 语法来实现懒加载。例如:

import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/home',
      name: 'Home',
      component: () => import('@/components/Home')
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('@/components/About')
    }
  ]
});

在上述代码中,component: () => import('@/components/Home') 表示当访问 /home 路由时,才会加载 Home 组件,而不是在应用启动时就加载。这样可以显著提高应用的首屏加载速度。

7.2 避免不必要的重渲染

当路由参数变化但组件实例复用时,要注意避免不必要的重渲染。如前文所述,我们可以通过监听 $route 的变化来处理参数变化,并且只在必要时重新获取数据,而不是每次都重新渲染整个组件。

例如,在 ProductDetail 组件中,我们只在参数 id 变化时重新获取商品详情数据,而不是重新渲染整个组件:

export default {
  data() {
    return {
      productId: null,
      productData: null
    };
  },
  created() {
    this.productId = this.$route.params.id;
    this.fetchProductData();
  },
  watch: {
    $route(to, from) {
      if (to.params.id!== from.params.id) {
        this.productId = to.params.id;
        this.fetchProductData();
      }
    }
  },
  methods: {
    fetchProductData() {
      // 根据 productId 去获取商品详情数据
    }
  }
};

通过这种方式,可以提高组件的性能,特别是在频繁切换路由参数的场景下。

8. 处理 404 页面

在单页应用中,处理 404 页面是很重要的。我们可以通过定义一个通配符路由来实现 404 页面的展示。

在路由配置中添加如下内容:

import Vue from 'vue';
import Router from 'vue-router';
import NotFound from '@/components/NotFound';

Vue.use(Router);

export default new Router({
  routes: [
    // 其他路由配置
    {
      path: '*',
      name: 'NotFound',
      component: NotFound
    }
  ]
});

NotFound.vue 组件中,我们可以展示一个友好的 404 页面提示:

<template>
  <div>
    <h1>404 - Page Not Found</h1>
  </div>
</template>

这样,当用户访问一个不存在的路由时,就会展示 404 页面。同时,我们还可以在 NotFound 组件中添加一些引导用户返回首页或其他页面的功能,提升用户体验。

9. 与服务器端渲染(SSR)结合

在一些场景下,我们可能需要将 Vue Router 与服务器端渲染(SSR)结合使用。在 SSR 中,路由配置和导航守卫的处理会略有不同。

例如,在 Nuxt.js(一个基于 Vue.js 的 SSR 框架)中,路由配置是通过约定式的方式进行的。我们只需要在 pages 目录下创建相应的文件,Nuxt.js 会自动生成对应的路由。

假设我们有一个 pages/about.vue 文件,Nuxt.js 会自动生成一个 /about 的路由。

在导航守卫方面,Nuxt.js 提供了 middleware 机制。例如,我们可以创建一个 middleware/auth.js 文件来验证用户登录状态:

export default function ({ store, redirect }) {
  const isLoggedIn = store.state.isLoggedIn;
  if (!isLoggedIn) {
    return redirect('/login');
  }
}

然后在页面组件中使用这个 middleware

export default {
  middleware: 'auth'
};

通过与 SSR 结合,我们可以提高应用的首屏加载速度,并且有利于搜索引擎优化(SEO)。

10. 测试 Vue Router

对 Vue Router 进行测试可以确保路由功能的正确性和稳定性。我们可以使用 Jest 和 Vue Test Utils 来进行测试。

10.1 测试路由配置

我们可以测试路由是否正确映射到对应的组件。例如,测试 Home 路由是否正确映射到 Home 组件:

import { createLocalVue, mount } from '@vue/test - utils';
import VueRouter from 'vue-router';
import Home from '@/components/Home';
import router from '@/router';

const localVue = createLocalVue();
localVue.use(VueRouter);

describe('Router', () => {
  it('should map / to Home component', () => {
    const route = router.resolve('/');
    expect(route.component).toBe(Home);
  });
});

在上述代码中,我们使用 router.resolve 方法来解析路由,并验证解析后的组件是否为预期的 Home 组件。

10.2 测试导航守卫

我们可以测试导航守卫是否按照预期工作。例如,测试全局前置守卫是否能正确拦截未登录用户访问受保护页面:

import { createLocalVue, mount } from '@vue/test - utils';
import VueRouter from 'vue-router';
import router from '@/router';

const localVue = createLocalVue();
localVue.use(VueRouter);

describe('Navigation Guards', () => {
  it('should redirect to Login if not logged in', async () => {
    const to = { name: 'Home' };
    const from = { name: 'Login' };
    const next = jest.fn();
    await router.beforeEach(to, from, next);
    expect(next).toHaveBeenCalledWith({ name: 'Login' });
  });
});

在上述代码中,我们模拟了一个未登录用户访问 Home 页面的场景,并验证导航守卫是否会将用户重定向到 Login 页面。

通过对 Vue Router 进行全面的测试,我们可以在开发过程中及时发现问题,提高应用的质量。

通过以上关于 Vue Router 的最佳实践与代码规范建议,希望能帮助开发者在使用 Vue Router 构建单页应用时,编写出更加健壮、可维护且高性能的代码。无论是小型项目还是大型企业级应用,合理运用这些方法都能提升开发效率和用户体验。