为了提高自己的全栈能力,学完Vue的我,又开始学习了Nuxt,以下就是我用Nuxt创建了一个简单的论坛。
1.Nuxt是什么?
Nuxt.js 是一个基于 Vue.js 的框架,主要用于构建服务端渲染(SSR)或静态网站生成(SSG)的应用。它提供了一些开箱即用的功能,简化了 Vue 应用的开发过程。
主要特点:
- 服务端渲染:Nuxt.js 可以让你的 Vue 应用在服务器上渲染,提升页面加载速度和 SEO 优化。
- 静态网站生成:可以将应用生成静态文件,便于部署和快速加载。
- 路由自动生成:Nuxt 会根据
pages目录中的文件自动生成路由,不需要手动配置。 - 模块化架构:内置许多模块,支持状态管理(Vuex)、API 请求、PWA 等功能,方便扩展。
- 插件支持:可以在应用中轻松引入各种插件,增强功能。
生活中的例子:
想象一下,你想做一个电商网站。使用 Nuxt.js,你可以:
- 提升 SEO:通过 SSR,让搜索引擎更容易抓取页面内容,增加曝光率。
- 快速加载:生成静态页面,使用户在访问时能更快看到商品。
- 简单路由:只需在
pages文件夹中创建对应的文件,自动生成商品详情、购物车等页面。
帮助你更高效地开发现代化的 Vue 应用,特别是在 SEO 和性能方面表现优异。
2.ssr是什么?spa又是什么?
SSR(Server-Side Rendering)和 SPA(Single Page Application)是两种不同的前端渲染方式,各有优缺点。
1. SSR(服务端渲染)
服务端渲染指的是网页的内容在服务器端生成后,发送到客户端,浏览器直接显示完整的 HTML 页面。
工作流程:
- 用户请求一个网页时,服务器先把页面的 HTML 生成好,带着数据一起返回给用户。
- 浏览器拿到这个完整的页面后直接渲染出来,用户立即看到页面内容。
- 当页面加载完成后,再通过 JavaScript 使页面变得“互动”。
优点:
- SEO 友好:因为搜索引擎能抓取到完整的 HTML 内容。
- 首屏加载快:用户可以快速看到页面内容,因为不需要先加载 JavaScript 再渲染页面。
缺点:
- 服务器压力大:每次请求都需要服务器生成完整的页面。
- 交互延迟:页面初始加载后,直到 JavaScript 完全加载,页面交互才能生效。
2. SPA(单页应用)
单页应用指的是整个应用只有一个 HTML 页面,所有页面内容和交互都是通过 JavaScript 动态加载和切换的。
工作流程:
- 首次加载时,服务器只返回一个空的 HTML 壳和 JavaScript 文件。
- JavaScript 运行后,通过 API 请求数据并渲染页面内容。
- 后续页面切换时,只会加载部分内容,不会刷新整个页面。
优点:
- 流畅的用户体验:页面切换无需刷新,用户体验更好。
- 减少服务器负载:页面的大部分逻辑都在客户端处理,减少了服务器压力。
缺点:
- 首屏加载慢:用户必须先下载 JavaScript 文件才能渲染页面内容。
- SEO 不友好:因为初始 HTML 为空,搜索引擎可能抓取不到有价值的内容(可以通过 SSR 或其他方式解决)。
ssr和spa的对比:
ssr:就像去餐馆点菜,服务员会把所有菜肴准备好,一起送到你的桌上,你马上就能开始吃。
spa:就像吃自助餐,开始的时候拿一个空盘子,自己去拿食物,后续想吃什么就直接去取,不用每次都回到桌子上等服务员端上菜。
总结:
- SSR 更适合需要快速加载和注重 SEO 的场景,如新闻网站、博客等。
- SPA 适合需要高频交互和流畅体验的场景,如社交网络、后台管理系统等。
用Nuxt开发论坛
服务端渲染语言Nuxt + 强类型约束TS + 模拟后端接口json-server 实现一个简易的论坛。
如何使用 Nuxt.js
1. 安装 Nuxt 项目
首先,通过命令行工具创建 Nuxt.js 项目:
npx nuxi init <project-name>
cd <project-name>
npm install
这里 nuxi 是 Nuxt 3 的 CLI 工具,用来初始化和管理 Nuxt 项目。
2. 运行 Nuxt 项目
安装完所有依赖后,可以启动开发服务器:
npm run dev
默认情况下,Nuxt 开发服务器运行在 http://localhost:3000。
3. 目录结构
Nuxt 有特定的目录结构,它基于文件系统来定义路由、布局、页面等。一个标准的 Nuxt 项目通常包含以下目录:
pages/:页面文件夹,文件名决定了路由。例如pages/index.vue对应/路由,pages/about.vue对应/about。layouts/:用于定义布局,页面可以继承不同的布局。components/:用于存放 Vue 组件。store/:用于定义 Vuex store 文件,用于管理全局状态。static/:存放静态文件,如图片、字体等。
4. 创建页面和路由
Nuxt 自动根据 pages 目录下的文件生成路由。例如,创建以下文件结构:
pages/
├── index.vue
├── about.vue
index.vue 对应 / 路由,about.vue 对应 /about 路由。
5. 使用布局(layouts)
Nuxt 支持布局系统,可以为页面定义不同的布局。默认布局文件是 layouts/default.vue,每个页面会自动使用该布局。
例如,定义一个布局:
<!-- layouts/default.vue -->
<template>
<div>
<header>
<h1>My Website Header</h1>
</header>
<nuxt />
<footer>
<p>Footer</p>
</footer>
</div>
</template>
页面的内容会被插入 <nuxt /> 的位置。
6. 使用 Vuex(store)
如果你需要全局状态管理,Nuxt 内置对 Vuex 的支持。创建 store/index.js 文件,Nuxt 会自动处理。
// store/index.js
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state) {
state.counter++
}
}
然后可以在页面或组件中使用它:
<template>
<div>
<p>Counter: {{ $store.state.counter }}</p>
<button @click="$store.commit('increment')">增加计数</button>
</div>
</template>
7. API 请求
Nuxt 提供了 asyncData 方法,允许你在页面加载前获取数据。这个方法会在服务端或客户端执行,取决于页面的渲染模式。
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
</template>
<script>
export default {
async asyncData({ $axios, params }) {
const post = await $axios.$get(`/api/posts/${params.id}`)
return { post }
}
}
</script>
如何用json-server
1. 安装 json-server
首先,你需要在项目中安装 json-server。可以通过 npm 或 yarn 安装。
npm install -g json-server
全局安装后,你可以在任何地方使用 json-server。
2. 创建一个 JSON 文件
接下来,你需要创建一个模拟数据库文件,通常叫做 db.json,这个文件会作为 json-server 的数据源。
{
"posts": [
{
"id": 1,
"title": "如何吸引野生画眉鸟到你的花园?",
"content": "画眉鸟以其悦耳的鸣叫闻名。要吸引这些美丽的鸟儿到你的花园,可以在庭院中种植它们喜欢的植物,如灌木和果树,或者放置鸟食器,提供新鲜的水果和昆虫饲料。",
"user": {
"id": 2,
"name": "鸟鸣之声"
}
},
{
"id": 2,
"title": "识别常见的城市鸟类:麻雀与鸽子",
"content": "在城市环境中,麻雀和鸽子是最常见的鸟类。麻雀通常小巧,身体棕色,喜欢成群结队,而鸽子体型较大,多见于公园和广场附近。通过观察它们的体型和行为,很容易区分这两种鸟类。",
"user": {
"id": 3,
"name": "飞鸟观察家"
}
},
{
"id": 3,
"title": "夏季如何为鸟类提供合适的饮水?",
"content": "夏天鸟类需要充足的水分,尤其是在干旱地区。你可以在庭院中放置浅水盘,每天更换新鲜的水,并确保水盆安全、无害,让鸟类能安心饮水。",
"user": {
"id": 4,
"name": "自然守护者"
}
},
{
"id": 4,
"title": "鸟类迁徙的秘密:如何跟踪候鸟的迁徙路径?",
"content": "通过安装GPS设备,科学家们能够跟踪候鸟的迁徙路径,了解它们的迁徙习惯和栖息地变化。这些数据对保护濒危鸟类具有重要意义。",
"user": {
"id": 5,
"name": "迁徙研究员"
}
},
{
"id": 5,
"title": "鹦鹉的饲养技巧:从食物到互动",
"content": "饲养鹦鹉不仅仅是提供食物,还要注重与它们的互动。鹦鹉喜欢玩具和挑战,给它们提供丰富的精神刺激对健康有好处。食物方面,可以提供多种水果、种子和蔬菜。",
"user": {
"id": 6,
"name": "鹦鹉达人"
}
},
{
"id": 6,
"title": "夜莺:夜晚的歌手",
"content": "夜莺以其夜晚优美的鸣唱而闻名。夜莺通常在春季开始鸣唱,主要为了吸引伴侣和划定领地。它们的鸣叫节奏复杂,频率多变,非常动听。",
"user": {
"id": 7,
"name": "夜鸟迷"
}
},
{
"id": 7,
"title": "如何搭建合适的鸟巢箱?",
"content": "自制鸟巢箱可以吸引鸟类在你家附近筑巢。选择合适的材料如天然木材,确保鸟巢的通风和排水,并根据目标鸟类调整入口大小。放置在高处且不易被捕食者攻击的位置是关键。",
"user": {
"id": 8,
"name": "DIY爱好者"
}
},
{
"id": 8,
"title": "冬季如何帮助鸟类过冬?",
"content": "冬天食物匮乏,鸟类需要额外的能量来抵御寒冷。你可以为它们提供高能量的种子和坚果,或者安装鸟食器,并在寒冷的日子定期补充食物。",
"user": {
"id": 9,
"name": "冬日守护者"
}
},
{
"id": 9,
"title": "金翅雀的饲养与繁殖注意事项",
"content": "金翅雀是一种美丽的鸟类,饲养它们需要特别注意饮食搭配,多提供新鲜的蔬果和种子。繁殖期间要确保它们有安静的环境,避免过度打扰。",
"user": {
"id": 10,
"name": "金翅爱好者"
}
},
{
"id": 10,
"title": "发现稀有鸟类的乐趣:我在森林中的一次探险",
"content": "上周,我在森林探险时意外发现了一只罕见的蓝翡翠!它的羽毛色彩斑斓,十分惊艳。这是一次令人难忘的经历,让我更加热爱观鸟。",
"user": {
"id": 11,
"name": "森林探险家"
}
}
]
}
3. 启动 json-server
在包含 db.json 文件的目录下启动 json-server:
json-server --watch db.json --port
默认情况下,它会在 http://localhost:3000 上运行
4. 自定义端口
如果你想在自定义端口上运行 json-server,可以使用 --port 选项。例如,运行在 5000 端口:
json-server --watch db.json --port 5000
项目
内容列表像掘金一样,点进去显示详细
用Nuxt能有很好SEO,利于网站被访问,浏览器能读取到数据,根据数据推荐给用户,手机端应用一般不会用Nuxt去写。
核心项目片段:
<template>
<div>
<h1>内容列表</h1>
<div class="post-list" v-if="posts !== null">
<div v-for="post in posts" :key="post.id">
<div>
<div>
<NuxtLink :to="`/posts/${post.id}`">{{ post.title }}</NuxtLink>
</div>
<div>{{ post.content}}</div>
<div>
- <small>{{ post.user.name }}</small>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const {
data:posts,
pending,
refresh,
error
} = await useApiFetch(()=> `posts`)
</script>
这里自定义了hooks函数,在useApiFetch中去写了useFetch()
<template>
<div class="app">
<header>
<div>
<span></span>
<NuxtLink to="/">{{ name }}</NuxtLink>
</div>
<nav>
<div>
<NuxtLink to="/about">关于</NuxtLink>
</div>
<div>
<NuxtLink to="/posts">内容</NuxtLink>
</div>
<div>
<NuxtLink to="/point">特点</NuxtLink>
</div>
<div>
<NuxtLink to="/culture">文化</NuxtLink>
</div>
</nav>
<div>
<div v-if="currentUser">
<NuxtLink to="/create">
<img src="/icons/add.svg" alt="添加内容">
</NuxtLink>
</div>
<div v-if="!currentUser">
<NuxtLink to="/login">
<img src="../static/icons/account.svg" alt="登录">
</NuxtLink>
</div>
</div>
</header>
<main>
<slot></slot>
</main>
<footer class="footer">
<div class="footer-container">
<div class="footer-links">
<a href="#">联系我们</a>
<a href="#">隐私政策</a>
<a href="#">使用条款</a>
</div>
<div class="footer-copyright">
© 2024 小鸟论坛 - 热爱自然,守护鸟类
</div>
</div>
</footer>
</div>
</template>
<script lang="ts" setup>
const name = '小鸟论坛'
const currentUser = false
</script>
<style>
@import '../assets/styles/default.css'
</style>
内置好的NuxtLink,其实和router-link一样的用法。
export const useApiFetch = (
api : string | (() => string)
) => {
return useFetch(api,{
baseURL:'http://localhost:1234',
onRequest:async(context) =>{
}
})
}
自定义的hooks函数,useApiFetch
body {
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
main {
font-size: 18px;
line-height: 1.8;
}
h1 {
font-size: 32px;
}
a {
color: black;
text-decoration: none;
}
button {
border: none;
outline: none;
background: #ededed;
padding: 6px 16px;
font-size: 14px;
border-radius: 5px;
color: #666;
cursor: pointer;
border: 1px solid transparent;
margin-bottom: 16px;
margin-top: 8px;
}
button:hover {
color: #000;
}
button ~ button {
margin-left: 8px;
}
button.primary {
background: #000;
color: #ededed;
}
button.primary:hover {
color: #fff;
}
button.bordered {
background: none;
border: 1px solid #eaeaea;
}
input,
textarea {
margin-bottom: 16px;
margin-right: 16px;
}
pre {
font-size: 16px;
padding: 8px;
}
label {
margin-right: 16px;
}
input[type='submit'] {
border: none;
outline: none;
background: #ededed;
padding: 6px 16px;
font-size: 14px;
border-radius: 5px;
color: #666;
cursor: pointer;
border: 1px solid transparent;
}
input[type='submit']:active {
color: #000;
}
input[type='text'],
input[type='password'],
textarea {
min-width: 360px;
}
input[type='text'],
input[type='password'],
textarea,
select {
border: 1px solid #eaeaea;
padding: 8px;
border-radius: 5px;
font-size: 14px;
}
input[type='checkbox'] {
transform: scale(1.3);
}
/* App */
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header */
.app > header {
display: flex;
align-items: center;
padding: 8px 16px;
border-bottom: 1px solid #eaeaea;
margin-bottom: 24px;
}
.app > header > div:nth-child(1) {
display: flex;
align-items: center;
font-size: 22px;
font-weight: bold;
margin-right: 56px;
}
.app > header > div:nth-child(1) > span {
font-size: 32px;
padding-right: 8px;
}
.app > header > nav {
display: flex;
flex-grow: 1;
color: #666;
}
.app > header > nav > div {
margin: 0 16px;
}
.app > header > nav ~ div {
display: flex;
}
.app > header > nav ~ div > div {
margin-left: 8px;
}
/* Main */
.app > main {
flex-grow: 1;
padding: 8px 16px;
display: flex;
flex-direction: column;
}
.post-list > div {
display: flex;
margin-bottom: 24px;
}
.post-list > div > div {
margin-right: 16px;
}
.post-list > div > div > img {
width: 64px;
height: 64px;
object-fit: cover;
border-radius: 50%;
}
.post-list > div > div > div:nth-child(1) {
font-weight: bold;
}
.post-list > div > div > div:nth-child(3) {
font-size: 14px;
}
.post-list > div > div > div:last-child {
display: flex;
margin-top: 8px;
}
.post-list > div > div > div:last-child img {
width: 20px;
}
.post-list > div > div > div:last-child > div {
margin-right: 8px;
}
.router-link-active {
font-weight: bold;
color: black;
}
.footer {
background-color: #2c3e50; /* 深色背景 */
color: #ecf0f1; /* 浅色字体 */
padding: 20px 0; /* 上下内边距 */
text-align: center; /* 文字居中 */
}
.footer-container {
max-width: 1200px; /* 限制内容最大宽度 */
margin: 0 auto; /* 居中对齐 */
display: flex;
flex-direction: column;
align-items: center; /* 垂直居中 */
}
.footer-links {
margin-bottom: 10px;
}
.footer-links a {
color: #ecf0f1; /* 链接颜色 */
margin: 0 15px; /* 链接之间的间距 */
text-decoration: none; /* 去除下划线 */
font-size: 14px;
}
全局样式,layouts->defaults.css
总结
当你学完Vue的生态,再学Nuxt的时候,会发现Nuxt会方便很多,很多东西都是直接用的,相关的好处也是显而易见的。服务端渲染(SSR)与静态生成支持,提升 SEO,文件系统路由,开发更高效,灵活的布局系统,统一设计风格,自动化的 SEO 优化。