前言
上篇文章中我们初始化了一个 nuxt
的项目,对 nuxt
项目结构有了基本的认识。 本篇开始来完成 nuxt
的第一个简单 demo
。
项目分析
首先想实现一个非常简单的demo来练手,经过思考后梳理如下页面:
- 首页
- 热搜列表页
- 商品列表页
- 关于页
nuxt3中的网络请求方式
首先要了解一下 nuxt3
中的网络请求。
useFetch
useFetch
是 Nuxt3
提供的一个特殊的组合式 API
,用于在组件中执行数据获取请求。它的主要特点是能够与 Nuxt
的服务端渲染(SSR
)无缝集成,自动处理请求的生命周期,并支持对数据进行预获取(Prefetch
)。useFetch
支持同时在客户端和服务端运行,并能够自动识别执行环境。
特性
-
自动
SSR
支持:能够在服务端预取数据,并在客户端进行数据的重用。 -
支持懒加载(
Lazy fetch
)和缓存。 -
与
Vue3
的响应式系统集成,支持数据的自动更新。 -
可以与其他组合式
API
(如useAsyncData
)结合使用,方便管理异步数据。 -
使用方法
js
<template>
<div>
<h1 v-if="data">{{ data.title }}</h1>
<p v-if="error">{{ error.message }}</p>
<p v-if="pending">Loading...</p>
</div>
</template>
<script setup>
import { useFetch } from 'nuxt/app'
const { data, error, pending } = useFetch('https://api.example.com/data')
</script>
$fetch
$fetch
是一个与 useFetch
不同的工具,它是 Nuxt3
内置的一个轻量级的 HTTP
请求库,用于在代码的任意地方执行 API
请求。相比 useFetch
,它更类似于 axios
或 fetch API
,但在 Nuxt
环境下经过了优化。
特性
-
支持在任何地方使用:包括
Vue
组件、Nuxt
插件、服务端中间件等。 -
内置对跨域、错误处理、超时等常见问题的处理。
-
灵活性高:可以自定义请求头、请求方法、序列化参数等。
-
支持客户端和服务端环境下的无缝运行。
-
使用方法
js
<template>
<div>
<h1 v-if="data">{{ data.title }}</h1>
<p v-if="error">{{ error.message }}</p>
<p v-if="pending">Loading...</p>
</div>
</template>
<script setup>
import { useFetch } from 'nuxt/app'
const { data, error, pending } = useFetch('https://api.example.com/data')
</script>
usefetch 和 $fetch 使用场景
使用 useFetch
:
diff
- 当你需要在组件中直接获取数据并将其展示时,特别是需要处理加载状态和错误。
- 如果依赖于 SSR,并希望在服务器端预加载数据。
使用 $fetch
:
markdown
- 当需要在 `setup` 函数中执行简单的 API 请求,或者在某些复杂逻辑中需要灵活的请求时。
- 适合在组合式 API 或与其他异步操作结合使用。
源码目录结构

conponents
全局 conponents
下创建 header
目录,创建 index.vue
header 组件内容
js
<template>
<div class="header flex flex-space-between">
<div class="name mouse-pointer" @click="backHome()">
{{ name }}
</div>
<div class="nav">
<div class="center">
<span class="center__item hover-shadow mr-8" @click="skip('hotSearchList')">热搜列表</span>
<span class="center__item hover-shadow mr-8" @click="skip('productList')">商品列表</span>
<span class="center__item hover-shadow mr-8" @click="skip('agent')">关于代理</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const name = "@丛林迷彩驿站"
const backHome = () => {
navigateTo(`/`);
};
const skip = (path: string) => {
navigateTo(`/${path}`);
};
</script>
<style scoped lang="less">
.header {
padding: 0 10px;
height: 45px;
line-height: 45px;
border-bottom: 1px solid rgb(163, 218, 165);
.nav {
.center__item {
margin: 0 10px;
cursor: pointer;
}
}
}
</style>
这里可以发现点击 nva
时候, slip
方法传入对应页面文件夹的名称,然后直接使用 navigateTo()
方法进行跳转即可。 这也就是自动化路由的效果。
layouts
layouts
目录下创建默认公共模板 default
组件,我这里很简单,一个 header
组件,然后就是 main 部分。如果需要也可以有底部组件。
default.vue
js
<template>
<!-- 头部 -->
<Header></Header>
<div class="main full">
<!-- 内容 -->
<NuxtPage />
</div>
</template>
pages
首页
- index.vue
js
<script lang="ts" setup>
import { reactive } from "vue";
const data = reactive({
introduce: "hello,欢迎来到 @丛林迷彩驿站",
desc: "这里提供一些丛林迷彩系列产品,欢迎咨询,诚信为本,非诚勿扰!",
});
</script>
<template>
<div class="home full flex-xy-center">
<div class="introduce">{{ data.introduce }}</div>
<div class="desc">{{ data.desc }}</div>
<!-- 首页底部自定义动画 -->
<Animation></Animation>
</div>
</template>
<style scoped lang="less">
.home {
flex-direction: column;
line-height: 40px;
background: url('@/static/img/bg.jpg') no-repeat center/ 100% 100%;
.introduce{
font-size: 20px;
}
/* 移动端兼容 */
@media (max-width: 600px) {
.desc {
text-align: center;
}
}
}
</style>
热搜页
js
<template>
<div class="hotSearchList full">
<div class="list flex">
<div class="item" v-for="item in cardList" :key="item.api">
<div class="header">
<div class="flex-space-between">
<div class="title">【{{ item.data.title }}】平台</div>
<div class="type">{{ item.data.type }}</div>
</div>
</div>
<div class="bodyList">
<Card :objData="item.data"></Card>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Card from "./components/Card.vue";
import { useFetch } from "nuxt/app";
import type { CardItem, HotSearchList } from "@/tpyes/HotSearchPlatform";
const activeList = [
"baidu",
"juejin",
"toutiao",
"qq-news",
"douyin",
"weibo",
];
const cardList = ref<CardItem[]>([]);
/* 获取平台热搜数据 */
const getAllhost = async (url: string) => {
const { data, error } = await useFetch(url, { key: `${url}_${Date.now()}` });
// 检查请求是否成功
if (error.value) {
console.error("Error fetching hot search data:", error.value);
return; // 处理错误,返回
}
// 提取数据
const responseData = data.value as HotSearchList;
// 验证数据格式
if (!responseData || responseData.code !== 200) {
console.warn("Unexpected response format:", responseData);
return;
}
// 处理有效数据
return responseData;
};
const init = async () => {
await nextTick();
const results = await Promise.all(
activeList.map(async (name) => {
const apiUrl = `/api/${name}?cache=true`;
const data = await getAllhost(apiUrl);
return {
name,
api: apiUrl,
data,
} as CardItem;
})
);
cardList.value = results;
};
init();
</script>
<style scoped lang="less">
.hotSearchList {
padding: 10px 20px;
.list {
flex-wrap: wrap;
}
.item {
flex: 1 1 calc(33% - 16px); /* 3列布局 */
max-width: calc(32% - 16px); /* 限制最大宽度 */
margin-bottom: 10px;
background-color: #abd3ad;
border-radius: 5px;
margin: 0 2% 3% 0;
.header {
padding: 5px;
.type {
font-weight: 500;
}
.api {
white-space: nowrap;
}
}
.bodyList {
width: 100%;
height: 340px;
}
}
/* 移动端兼容 */
@media (max-width: 600px) {
.item {
flex: 1 1 100%; /* 单列布局 */
max-width: 100%; /* 限制最大宽度 */
.header {
.api {
white-space: normal;
}
}
}
}
}
</style>
页面效果

遇到问题
default.vue 中使用 components 目录下的组件不生效
原因:
没有自动注册, nuxt源码中是不需要src这一层目录的, 去除src后就可以了
nuxt项目启动后浏览器第一次访问正常,当打开浏览器调试工具后页面访问失败
在我调试移动端时发现只要打开调试工具,选择手机模拟器后,然后在刷新页面就无法访问了,关闭调试窗口刷新又好了
先开始我一脸懵逼,以为是代码哪里有问题,甚至一度怀疑nuxt项目需要配置才能支持调试工具的手机模拟器😂
最后求助网友后得到启发,会不会是调试工具设置了什么导致的。先开始我还不以为然,说没设置啥啊!
于是打开我的调试工具查看后,fuck!

网络选项下,是离线状态!!! 这他妈的怎么可能访问成功! 但是我不记得我设置过啊!!! 唉,裂开了要。设置为:已停用节流模式后问题解决。
接口跨域问题
nuxt.config.js
配置代理:
js
nitro: {
devProxy: {
"/api": {
target: "https://xxx",
changeOrigin: true,
},
},
},
本地开发环境接口跨域设置代理,如果是部署生产后需要使用 ngingx 进行代理转发接口。
list 数据出现重复
- 获取数据方法
js
/* 获取平台热搜数据 */
const getAllhost = async (url: string) => {
const { data, error } = await useFetch(url, { key: Date.now().toString() });
// 检查请求是否成功
if (error.value) {
console.error("Error fetching hot search data:", error.value);
return; // 处理错误,返回
}
// 提取数据
const responseData = data.value as HotSearchList;
// 验证数据格式
if (!responseData || responseData.code !== 200) {
console.warn("Unexpected response format:", responseData);
return;
}
// 处理有效数据
return responseData;
};

渲染出来的 list
数据有重复内容,当然我们请求的数据肯定不是重复的。
解决: 修改 useFeatch
的 key
参数
const { data, error } = await useFetch(url, { key:
{url}_{Date.now()} })
使用参数 url 拼接一个时间戳。
刷新页面请求失败

首次进入页面后接口请求都是 ok 的,但是刷新页面后所有接口都失败了。
解决:在 init 方法中添加:await nextTick();
即可。
nextTick()
确保你在进行操作时 DOM
已经更新。
js
const init = async () => {
await nextTick();
const results = await Promise.all(
activeList.map(async (name) => {
const apiUrl = `/api/${name}?cache=true`;
const data = await getAllhost(apiUrl);
return {
name,
api: apiUrl,
data,
} as CardItem;
})
);
cardList.value = results;
};
init();
总结
至此,使用 nuxt3
完成了一个非常简单的小demo。 在这个过程中遇到的一些问题也都最终解决了。