深入 Lyt.js 路由系统:L6 生态系统层的核心

6 阅读5分钟

深入 Lyt.js 路由系统:L6 生态系统层的核心

基于 Lyt.js v6.6.0 @lytjs/router@lytjs/router-fs 包源码,深入解析路由系统的设计理念、导航守卫、路由匹配和文件系统路由。

一、路由系统架构

在 Lyt.js v6.6.0 的 8 层架构中,路由系统位于 L6 生态系统层:

L5: 组件基础层
L4: 插件与适配层
L3: 核心运行时层
L2: 渲染引擎层
L1: 核心原语层L6: 生态系统层
  ├── @lytjs/router (路由系统)
  ├── @lytjs/router-fs (文件系统路由)
  ├── @lytjs/api (API 路由)
  ├── @lytjs/store (状态管理)
  ├── @lytjs/ui (UI 组件库)
  └── ...

二、快速开始

import { createApp } from '@lytjs/core'
import { createRouter, createWebHistory } from '@lytjs/router'
import Home from './views/Home.lyt'
import About from './views/About.lyt'
import User from './views/User.lyt'

const router = createRouter({
  mode: 'history',
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About },
    { path: '/user/:id', component: User },
  ]
})

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

三、路由模式

Lyt.js 支持两种路由模式:

3.1 History 模式

使用 HTML5 History API,适合有服务器配置的场景:

import { createWebHistory } from '@lytjs/router'

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

特点

  • URL 更加美观(/user/123
  • 需要服务器配置处理 404
  • 支持 history.pushStatehistory.replaceState
3.2 Hash 模式

使用 URL hash(#),无需服务器配置:

import { createHashHistory } from '@lytjs/router'

const router = createRouter({
  mode: 'hash',
  routes: [...]
})

特点

  • 无需服务器配置
  • URL 包含 #/#/user/123
  • 适合静态托管环境

四、路由定义

4.1 基础路由
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/contact', component: Contact },
]
4.2 动态路由
const routes = [
  { path: '/user/:id', component: User },
  { path: '/post/:category/:slug', component: Post },
  { path: '/order/:id?', component: Order }, // 可选参数
]
4.3 嵌套路由
const routes = [
  {
    path: '/user/:id',
    component: UserLayout,
    children: [
      { path: '', component: UserProfile },
      { path: 'posts', component: UserPosts },
      { path: 'settings', component: UserSettings },
    ]
  }
]
4.4 命名路由
const routes = [
  {
    path: '/user/:id',
    name: 'user',
    component: User
  }
]

// 编程式导航
router.push({ name: 'user', params: { id: '123' } })
4.5 别名路由
const routes = [
  { path: '/home', component: Home, alias: '/' }
]

五、路由参数

5.1 组件中获取参数
// 方式一:通过 inject
import { useRoute } from '@lytjs/router'

const route = useRoute()
console.log(route.params.id)    // URL 参数
console.log(route.query.search) // 查询参数
console.log(route.hash)         // hash 值

// 方式二:通过 $route
defineComponent({
  template: '<p>User ID: {{ $route.params.id }}</p>'
})
5.2 参数变化监听
const route = useRoute()

// 监听参数变化
watch(
  () => route.params.id,
  (newId, oldId) => {
    console.log(`ID 从 ${oldId} 变为 ${newId}`)
    // 重新获取数据
  }
)

六、导航守卫

6.1 全局前置守卫
router.beforeEach((to, from, next) => {
  // to: 目标路由
  // from: 当前路由
  // next: 继续导航的函数
  
  if (to.meta.requiresAuth && !isLoggedIn()) {
    next('/login')
  } else {
    next()
  }
})
6.2 全局解析守卫
router.beforeResolve((to, from, next) => {
  // 在导航被确认前,所有组件内守卫和异步路由组件被解析之后调用
  // 适合做数据预获取
  fetchData(to.params.id).then(data => {
    to.meta.data = data
    next()
  })
})
6.3 全局后置钩子
router.afterEach((to, from) => {
  // 导航确认后调用
  document.title = to.meta.title || 'Lyt.js App'
  analytics.pageView(to.path)
})
6.4 路由独享守卫
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (isAdmin()) {
        next()
      } else {
        next('/403')
      }
    }
  }
]
6.5 组件内守卫
defineComponent({
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被确认前调用
    // 不能访问 this
    next(vm => {
      // 通过 vm 访问组件实例
    })
  },
  
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但该组件被复用时调用
    // 适合处理参数变化
  },
  
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问 this
    if (hasUnsavedChanges()) {
      const answer = confirm('有未保存的更改,确定离开吗?')
      if (answer) {
        next()
      } else {
        next(false)
      }
    } else {
      next()
    }
  }
})
6.7 导航守卫执行顺序
  1. 导航触发
  2. 失活的组件调用 beforeRouteLeave
  3. 全局 beforeEach
  4. 复用组件调用 beforeRouteUpdate
  5. 路由独享 beforeEnter
  6. 解析异步路由组件
  7. 激活组件调用 beforeRouteEnter
  8. 全局 beforeResolve
  9. 导航确认
  10. 全局 afterEach

七、编程式导航

7.1 基础导航
// 字符串路径
router.push('/user/123')

// 对象路径
router.push({ path: '/user/123' })

// 命名路由
router.push({ name: 'user', params: { id: '123' } })

// 带查询参数
router.push({ path: '/search', query: { q: 'vue' } })

// 替换当前记录
router.replace('/home')

// 前进/后退
router.go(1)   // 前进
router.go(-1)  // 后退
router.back()  // 后退
router.forward() // 前进
7.2 导航控制
// 取消导航
router.beforeEach((to, from, next) => {
  if (needsGuard(to)) {
    next(false) // 取消导航
  } else {
    next()
  }
})

// 重定向
router.beforeEach((to, from, next) => {
  if (to.path === '/old') {
    next('/new')
  } else {
    next()
  }
})

// 导航错误
router.onError(error => {
  console.error('导航错误:', error)
})

八、元信息(Meta)

8.1 定义 Meta
const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      requiresAuth: true,
      role: 'admin',
      title: '管理后台'
    }
  }
]
8.2 访问 Meta
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) {
    // 需要认证
  }
  if (to.meta.role === 'admin') {
    // 需要管理员权限
  }
  next()
})

九、路由懒加载

9.1 箭头函数
const routes = [
  { path: '/home', component: () => import('./views/Home.vue') },
  { 
    path: '/about', 
    component: () => import('./views/About.vue')
      .then(m => m.default) // 支持 Promise
  }
]
9.2 路由懒加载 + Loading
import { defineAsyncComponent } from '@lytjs/core'

const routes = [
  {
    path: '/dashboard',
    component: defineAsyncComponent({
      loader: () => import('./views/Dashboard.vue'),
      loadingComponent: LoadingSpinner,
      errorComponent: ErrorBoundary,
      delay: 200,
      timeout: 3000
    })
  }
]
9.3 路由分组
// 将某个路由下的所有组件都打包在同一个异步块中
const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    children: [
      { path: 'users', component: () => import('./views/AdminUsers.vue') },
      { path: 'settings', component: () => import('./views/AdminSettings.vue') }
    ]
  }
]

十、过渡动画

// 路由过渡
defineComponent({
  template: `
    <Transition name="fade" mode="out-in">
      <router-view />
    </Transition>
  `
})

// CSS
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

十一、路由类型安全

Lyt.js 支持 TypeScript 类型推导:

import type { RouteRecordRaw } from '@lytjs/router'

const routes: RouteRecordRaw[] = [
  {
    path: '/user/:id',
    name: 'User',
    component: () => import('./views/User.vue'),
    props: true, // 将路由参数作为 props 传递给组件
    children: [
      {
        path: 'posts',
        component: () => import('./views/UserPosts.vue')
      }
    ]
  }
]

// 组件中使用
defineComponent({
  props: {
    id: String // 从路由参数自动获取
  },
  setup(props) {
    // props.id 已有类型
  }
})

十二、文件系统路由

Lyt.js v6.6.0 提供了 @lytjs/router-fs 包,支持基于文件系统的路由:

src/
└── pages/
    ├── index.lyt          → /
    ├── about.lyt          → /about
    ├── user/
    │   ├── index.lyt     → /user
    │   └── [id].lyt      → /user/:id
    └── blog/
        ├── index.lyt      → /blog
        └── [slug].lyt    → /blog/:slug
import { createFileSystemRouter } from '@lytjs/router-fs'

const router = createFileSystemRouter('./src/pages', {
  extensions: ['.lyt', '.vue'],
  // 导入模式
  importMode: 'async', // 'sync' | 'async' | (path) => Promise
  // 路由选项
  routes: {
    index: { name: 'home' },
    dynamic: { prefix: '[' }
  }
})

十三、在 v6.6.0 中的位置

路由系统在 8 层架构中的位置:

L1: 核心原语层
  ├── @lytjs/reactivity
  ├── @lytjs/vdom
  └── @lytjs/compiler
L2: 渲染引擎层
  ├── @lytjs/renderer
  ├── @lytjs/component
  └── @lytjs/dom-runtime
L3: 核心运行时层
  └── @lytjs/core
L4: 插件与适配层
  ├── @lytjs/plugin-auth (权限插件)
  └── ...
L5: 组件基础层
L6: 生态系统层 ← 当前层
  ├── @lytjs/router
  ├── @lytjs/router-fs
  ├── @lytjs/store
  └── ...
L7: 工程化工具层

路由系统依赖核心运行时层,同时被插件层(如权限插件)扩展。