软件开发 --- 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(内容相对固定)