GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

软件开发 --- vue之nuxt

1. Nuxt.js 路由配置

Nuxt.js 主要使用基于文件系统的路由(File System Routing),无需手动配置路由:

pages/
├── index.vue         # / 
├── about.vue        # /about
├── posts/
│   ├── index.vue    # /posts
│   └── [id].vue     # /posts/:id  动态路由
└── users/
    ├── profile.vue  # /users/profile
    └── settings/
        └── index.vue # /users/settings

 app.vue 入口文件

<template>
 <NuxtPage />
</template>

比如我们要访问 profile.vue 页面,直接 http://localhost/users/profile

 

 2.组件复用

 app.vue 首页 

<template>
 <NuxtLayout>    导航页位置
 <NuxtPage />    内容页位置
 </NuxtLayout>
</template>
layouts/default.vue 导航页
<template>
 <div>
 <nav>
 <NuxtLink to="/">Home</NuxtLink>
 <NuxtLink to="/about">About</NuxtLink>
 </nav>
 <main>
 <slot />
 </main>
 </div>
</template>

 

pages/index.vue 详情页
<template>
 <h1>Home</h1>
 ...
</template>

 

显示效果

 

Dynamic Route (动态路由)

[id].vue

<!-- pages/user/[id].vue -->
<template>
  <div>
    <h1>用户详情页</h1>
    <p>用户ID: {{ $route.params.id }}</p>
  </div>
</template>

<script>
export default {
  mounted() {
    // 访问 /user/123 时,id = "123"
    console.log('用户ID:', this.$route.params.id)
  }
}
</script>

 

访问: /user/123 

id=123作为路由的参数进行传递

 

Mixed Route (混合路由)

<!-- pages/my-[type].vue -->
<template>
  <div>
    <h1>我的{{ $route.params.type }}</h1>
    <div v-if="$route.params.type === 'orders'">
      <!-- 订单列表 -->
    </div>
    <div v-else-if="$route.params.type === 'profile'">
      <!-- 个人资料 -->
    </div>
  </div>
</template>

 

访问: /my-orders -> 显示订单列表

访问: /my-profile -> 显示个人资料

 

Optional Route (可选路由)

<!-- pages/products/[[category]].vue -->
<template>
  <div>
    <h1>产品列表</h1>
    <p v-if="$route.params.category">
      当前分类: {{ $route.params.category }}
    </p>
    <p v-else>
      显示所有产品
    </p>
  </div>
</template>

访问: /products -> 显示所有产品

访问: /products/electronics -> 显示电子产品

 

Catch-All Route (通配符路由)

<!-- pages/docs/[...slug].vue -->
<template>
  <div>
    <h1>文档页面</h1>
    <p>当前路径: {{ $route.params.slug.join('/') }}</p>
    <div class="breadcrumb">
      <span v-for="(part, index) in $route.params.slug" :key="index">
        / {{ part }}
      </span>
    </div>
  </div>
</template>

<script>
export default {
  mounted() {
    // 访问 /docs/api/auth/login 时
    // slug = ["api", "auth", "login"]
    console.log('路径数组:', this.$route.params.slug)
  }
}
</script>

访问: /docs/api/auth/login -> 显示API认证登录文档

实际使用示例

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // 动态路由
    {
      path: '/user/:id',
      component: () => import('@/pages/user/[id].vue')
    },
    
    // 混合路由
    {
      path: '/my-:type',
      component: () => import('@/pages/my-[type].vue')
    },
    
    // 可选路由
    {
      path: '/products/:category?',
      component: () => import('@/pages/products/[[category]].vue')
    },
    
    // 通配符路由
    {
      path: '/docs/:pathMatch(.*)*',
      component: () => import('@/pages/docs/[...slug].vue')
    }
  ]
})

export default router

 

访问参数

<script setup>
const params = useRoute().params    不需要显式导入useRoute
 </script>

 

 

SSR的状态共享

这个composables文件夹中的任何可组合函数都会自动导入到组件中

composables/useWidth.js

export function useWidth() {
  console.log('初始化 width 状态')
  
  return useState('width', () => {
    const value = 1000
    console.log(`width 状态值设置为: ${value}`)
    return value
  })
} 

 

useState 的回调函数只在服务器端运行一次

这个值会被序列化并发送到客户端

客户端会直接使用这个值,而不会重新执行这个函数

这样可以确保服务器端和客户端状态的一致性

pages/index.vue

<script setup>
const width = useWidth()  不需要导入
console.log(width.value)  相同值  
</script>

pages/about.vue

<script setup>
const width = useWidth()
console.log(width.value)  相同值 
</script>

 

 

 useFetch 和 API 的使用:

如果在组件中获取数据,请使用 useFetch

1. 获取用户列表的例子

前端页面 (pages/users/index.vue):

<template>
  <div>
    <h1>用户列表</h1>
    <!-- 显示加载状态 -->
    <div v-if="pending">加载中...</div>
    <!-- 显示错误信息 -->
    <div v-if="error">加载失败: {{ error.message }}</div>
    <!-- 显示用户列表 -->
    <ul v-if="data">
      <li v-for="user in data" :key="user.id">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>
  </div>
</template>

<script setup>
// 使用 useFetch 获取数据,包含了加载状态和错误处理
const { data, pending, error } = await useFetch('/api/users', {
  // 可以添加请求配置
  headers: {
    'Authorization': 'Bearer token'
  }
})

// 监听数据变化
watch(data, (newData) => {
  console.log('用户数据更新:', newData)
})
</script>

后端 API (server/api/users.js):    从服务器上具有相同名称的API文件中获取

export default defineEventHandler(async (event) => {   记住,如果在函数中使用 await,需要将函数声明为 async,vent 参数提供了请求的数据信息
  // 添加日志记录
  console.log('收到用户列表请求')

  try {
    // 模拟数据库查询
    const users = [
      { id: 1, name: '张三', email: 'zhangsan@example.com' },
      { id: 2, name: '李四', email: 'lisi@example.com' },
      { id: 3, name: '王五', email: 'wangwu@example.com' }
    ]

    // 模拟延迟
    await new Promise(resolve => setTimeout(resolve, 1000))

    return users
  } catch (error) {
    console.error('获取用户列表失败:', error)
    throw createError({
      statusCode: 500,
      message: '服务器错误'
    })
  }
})

 

2. 带参数的 API 请求例子

前端页面 (pages/users/[id].vue):

<template>
  <div>
    <h2>用户详情</h2>
    <div v-if="data">
      <p>姓名: {{ data.name }}</p>
      <p>邮箱: {{ data.email }}</p>
      <p>注册时间: {{ data.registerDate }}</p>
    </div>
  </div>
</template>

<script setup>
const route = useRoute()

// 获取特定用户信息
const { data } = await useFetch(`/api/users/${route.params.id}`, {
  // 请求参数
  query: {
    include: 'profile,orders'
  }
})
</script>

后端 API (server/api/users/[id].js):

export default defineEventHandler(async (event) => {
  // 获取路径参数
  const id = event.context.params.id
  
  // 获取查询参数
  const query = getQuery(event)
  
  console.log(`获取用户 ${id} 的信息,包含:`, query.include)

  // 模拟用户数据
  return {
    id: id,
    name: '张三',
    email: 'zhangsan@example.com',
    registerDate: '2024-01-01',
    // 根据 include 参数返回额外信息
    ...(query.include?.includes('profile') ? {
      profile: {
        avatar: 'https://example.com/avatar.jpg',
        bio: '这是个人简介'
      }
    } : {})
  }
})

 

 

渲染模式简单解释
想象一个网站,它可以用不同的方式来显示页面,就像可以用不同的方式来做一道菜:
服务器端渲染 (默认模式 { ssr: true })
服务器把页面处理好再发给浏览器

客户端渲染 ({ ssr: false })
分两次在浏览器完成渲染,第一次获取页面,第二次获取数据


静态生成 ({ prerender: true })
构建时直接生成静态页面


增量静态重生成 ({ isr: true })

ISR 工作原理,类似SSR
在构建时生成静态页面
设置一个重新生成时间(例如10分钟)
10分钟后,当有新请求来临时:
  先返回旧的缓存页面
  在后台触发页面重新生成
  更新完成后,后续访问将看到新内容
  直到下一个10分钟周期


过时后重新验证 ({ swr: true })

首次请求:
立即从缓存返回数据(如果有)
同时在后台发起网络请求获取最新数据
获取到新数据后自动更新界面

Nuxt 配置文件详解

export default defineNuxtConfig({
  routeRules: {
    // 1. 首页配置
    '/': { ssr: true },
    
    // 2. 后台管理页面配置
    '/admin/**': { ssr: false },
    
    // 3. 博客页面配置
    '/blog/**': { prerender: true },
    
    // 4. 产品列表配置
    '/products': { isr: true },
    
    // 5. 用户资料页配置
    '/user/profile': { swr: true }
  }
})

 

详细解释每种模式:
首页 - 服务器端渲染 ({ ssr: true })
路径:/
特点:每次访问都在服务器生成新页面
适用场景:
实时性要求高的内容
需要SEO的页面
动态内容较多的页面
2. 后台管理 - 客户端渲染 ({ ssr: false })
路径:/admin/**(匹配所有 admin 下的页面)
特点:完全在浏览器端渲染
适用场景:
后台管理系统
需要大量交互的页面
不需要SEO的内部系统
博客 - 静态生成 ({ prerender: true })
路径:/blog/**(匹配所有博客页面)
特点:构建时生成静态HTML
适用场景:
博客文章
文档页面
内容不经常变化的页面
4. 产品列表 - 增量静态重生成 ({ isr: true })
路径:/products
特点:定期重新生成页面
适用场景:
商品列表
定期更新的内容
需要缓存但又要定期更新的页面
用户资料 - 过时后重新验证 ({ swr: true })
路径:/user/profile
特点:先返回缓存,后台更新
适用场景:
用户个人信息
不要求绝对实时的数据展示
可以接受短暂旧数据的页面

上面的内容还是有点抽象,我将从流量的角度解释

1. 服务器端渲染 (SSR) { ssr: true },本质就是完整HTML响应

请求过程:

GET /products/123 HTTP/1.1
Host: example.com

响应内容:

HTTP/1.1 200 OK
Content-Type: text/html

<!DOCTYPE html>
<html>
  <head><title>iPhone 14</title></head>
  <body>
    <div id="app">
      <h1>iPhone 14</h1>
      <p>价格:¥5999</p>
      <p>库存:10件</p>
    </div>
  </body>
</html>

 

2. 客户端渲染 (CSR) { ssr: false },本质就是分步加载,先加载页面在加载数据

请求过程:

# 第一次请求:获取空壳
GET /admin HTTP/1.1
Host: example.com

# 第二次请求:获取数据
GET /api/admin/data HTTP/1.1
Host: example.com

 

响应内容:

# 第一次响应:返回空壳
HTTP/1.1 200 OK
Content-Type: text/html

<!DOCTYPE html>
<html>
  <head><title>管理后台</title></head>
  <body>
    <div id="app">
      <!-- 等待JS填充内容 -->
    </div>
    <script src="/app.js"></script>
  </body>
</html>

# 第二次响应:返回数据
HTTP/1.1 200 OK
Content-Type: application/json

{
  "users": [
    {"name": "张三", "role": "admin"}
  ]
}

 

3. 静态生成 (Static) { prerender: true }

请求过程

GET /about HTTP/1.1
Host: example.com

响应内容:

HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: public, max-age=31536000

<!DOCTYPE html>
<html>
  <head><title>关于我们</title></head>
  <body>
    <div id="app">
      <h1>公司简介</h1>
      <p>成立于2010年...</p>
    </div>
  </body>
</html>

 

有点类似ssr,本质上讲,主要区别总结:

  • 执行时机:
  • SSR:每次请求都执行
  • Static:构建时执行一次
  • 数据新鲜度:
  • SSR:实时数据
  • Static:构建时数据
  • 缓存策略:
  • SSR:通常不缓存或短期缓存
  • Static:长期缓存,通过CDN分发
  • 服务器负载:
  • SSR:每次请求都消耗服务器资源
  • Static:只在构建时消耗资源
  • 响应速度:
  • SSR:需要实时处理,相对较慢
  • Static:直接返回文件,极快

这就是为什么:

  • 商品详情用SSR(需要实时数据)
  • 公司介绍用Static(内容相对固定)

 

posted on 2024-12-01 17:28  GKLBB  阅读(2)  评论(0编辑  收藏  举报