Nuxt3框架入门:第一个简单demo

前言

上篇文章中我们初始化了一个 nuxt 的项目,对 nuxt 项目结构有了基本的认识。 本篇开始来完成 nuxt 的第一个简单 demo

项目分析

首先想实现一个非常简单的demo来练手,经过思考后梳理如下页面:

  • 首页
  • 热搜列表页
  • 商品列表页
  • 关于页

nuxt3中的网络请求方式

首先要了解一下 nuxt3 中的网络请求。

useFetch

useFetchNuxt3 提供的一个特殊的组合式 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,它更类似于 axiosfetch 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 数据有重复内容,当然我们请求的数据肯定不是重复的。

解决: 修改 useFeatchkey 参数

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。 在这个过程中遇到的一些问题也都最终解决了。

相关推荐
万事胜意50710 分钟前
前端切换Tab数据缓存实践
前端
渣渣宇a10 分钟前
Three_3D_Map 中国多个省份的组合边界绘制,填充背景
前端·javascript·three.js
点正13 分钟前
ResizeObserver 和nextTick 的用途
前端
zayyo16 分钟前
Web 应用轻量化实战
前端·javascript·面试
kovli19 分钟前
红宝书第十七讲:通俗详解JavaScript的Promise与链式调用
前端·javascript
lilye6620 分钟前
精益数据分析(19/126):走出数据误区,拥抱创业愿景
前端·人工智能·数据分析
李是啥也不会25 分钟前
Vue中Axios实战指南:高效网络请求的艺术
前端·javascript·vue.js
xiaoliang30 分钟前
《DNS优化真经》
前端
一只小海獭33 分钟前
了解uno.config.ts文件的配置项---转化器
前端
贾公子36 分钟前
MySQL数据库基础 === 约束
前端·javascript