阿里云主机折上折
  • 微信号
Current Site:Index > Refactor the routing API (createRouter)

Refactor the routing API (createRouter)

Author:Chuan Chen 阅读数:31094人阅读 分类: Vue.js

In the Vue.js ecosystem, routing management is one of the core capabilities of single-page applications. The createRouter API, as a central part of Vue Router, directly impacts the flexibility and maintainability of routing configurations. Below, we delve into specific practices from the perspectives of configuration structure, dynamic routing, navigation guards, and more.

Refactoring Basic Configuration

Traditional routing configurations are instantiated via new Router(), while createRouter adopts a more functional and declarative approach. A comparison of the two styles:

// Legacy approach
const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/', component: Home }
  ]
})

// Modern createRouter approach
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { 
      path: '/dashboard',
      component: () => import('./views/Dashboard.vue'),
      meta: { requiresAuth: true }
    }
  ]
})

Key improvements:

  1. Routing modes are explicitly declared via createWebHistory/createWebHashHistory.
  2. Dynamic import syntax is supported for components.
  3. The configuration object structure is flattened.

Handling Dynamic Routing

Runtime route injection is achieved via router.addRoute(), commonly used for permission-based routing:

// Asynchronously fetch permission-based routes
const loadRoutes = async () => {
  const dynamicRoutes = await fetch('/api/routes')
  dynamicRoutes.forEach(route => {
    router.addRoute({
      path: route.path,
      component: () => import(`./views/${route.component}.vue`)
    })
  })
  
  // 404 route must be added last
  router.addRoute({ path: '/:pathMatch(.*)', component: NotFound })
}

Removing dynamic routes requires named routes:

// Add a named route
router.addRoute({
  name: 'admin',
  path: '/admin',
  component: AdminPanel
})

// Remove by name
router.removeRoute('admin')

Optimizing Navigation Guards

The guard system consists of global, route-specific, and component-level guards. The new version introduces significant changes to the handling of the next parameter:

// Legacy guard
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) next('/login')
  else next()
})

// Modern recommended approach
router.beforeEach((to, from) => {
  if (to.meta.requiresAuth) return '/login'
  // No return implies continuation
})

Example of guard composition:

// Global before guard
router.beforeEach(async (to) => {
  const authStore = useAuthStore()
  if (to.meta.requiresAuth && !authStore.user) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
})

// Route-specific guard
const routes = [
  {
    path: '/user/:id',
    component: UserDetail,
    beforeEnter: (to) => {
      if (!isValidId(to.params.id)) return false
    }
  }
]

Extending Route Meta Information

The meta field supports type inference, enabling type safety with TypeScript:

declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean
    transitionName?: string
    permissionLevel: number
  }
}

const routes: RouteRecordRaw[] = [
  {
    path: '/admin',
    meta: {
      requiresAuth: true,
      permissionLevel: 5 // Automatic type checking
    }
  }
]

Integration with Composition API

Using routing-related properties in setup:

import { useRoute, useRouter } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    const router = useRouter()
    
    const navigate = () => {
      router.push({
        path: '/search',
        query: { q: route.query.filter }
      })
    }

    watch(
      () => route.params.id,
      (newId) => fetchData(newId)
    )
  }
}

Customizing Scroll Behavior

Fine-grained scroll control via scrollBehavior:

createRouter({
  scrollBehavior(to, from, savedPosition) {
    if (to.hash) {
      return {
        el: to.hash,
        behavior: 'smooth'
      }
    }
    return savedPosition || { top: 0 }
  }
})

Lazy Loading Strategies for Routes

Combining component loading with route transition animations:

const routes = [
  {
    path: '/settings',
    component: defineAsyncComponent({
      loader: () => import('./Settings.vue'),
      delay: 200, // Delay before showing loading
      timeout: 3000, // Timeout period
      loadingComponent: LoadingSpinner,
      errorComponent: ErrorDisplay
    }),
    meta: { transition: 'fade' }
  }
]

Testing Adaptations

Creating test router instances in unit tests:

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

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

await router.isReady() // Wait for router initialization

Performance Optimization Practices

Route code-splitting and preloading strategies:

const routes = [
  {
    path: '/reports',
    component: () => import(/* webpackChunkName: "analytics" */ './Reports.vue'),
    meta: {
      preload: true // Custom preload marker
    }
  }
]

router.beforeEach((to) => {
  if (to.meta.preload) {
    const component = to.matched[0].components.default
    component && typeof component === 'function' && component()
  }
})

Integration with State Management

Example of synchronizing Pinia with route state:

// stores/route.js
export const useRouteStore = defineStore('route', {
  state: () => ({
    previousRoute: null
  }),
  actions: {
    setPreviousRoute(route) {
      this.previousRoute = route
    }
  }
})

// Global after guard
router.afterEach((to, from) => {
  const routeStore = useRouteStore()
  routeStore.setPreviousRoute(from)
})

Route Component Communication

Receiving route parameters via props:

const routes = [
  {
    path: '/user/:id',
    component: UserProfile,
    props: route => ({
      id: route.params.id,
      query: route.query.search
    })
  }
]

// UserProfile.vue
defineProps({
  id: String,
  query: String
})

Transition Animation Control

Animation control based on route meta:

<template>
  <router-view v-slot="{ Component, route }">
    <transition :name="route.meta.transition || 'fade'">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

Error Handling Mechanisms

Global error capture example:

router.onError((error) => {
  if (error.message.includes('Failed to fetch dynamically imported module')) {
    showErrorToast('Module loading failed')
  }
})

Server-Side Rendering (SSR) Adaptation

Using createMemoryHistory in SSR mode:

export default (context) => {
  const router = createRouter({
    history: createMemoryHistory(),
    routes
  })
  
  context.router = router
  return router.isReady()
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.