Nuxt3 基础总结
前言
Nuxt3 的对比之前的2 和 1 ,只能感叹前端发展的越来越快了,不学无术
(PS :好好学,我还年轻 我还可以 每天进步一小点)
开发更快
打包更小
支持 vite
支持 vue3
支持自动引入
支持文件路由
支持布局系统
支持多种渲染模式
支持 typescript
支持 composition-api
安装NUXT3 需要node 大于16的版本
brew 更新 node
brew update
brew upgrade node
还有就是 node 安装
npm install -g
npm install -g
sudo n stable
最后查看node版本
然后 安装官网的来
npx nuxi@latest init <project-name>
因为家里的网 没有那么快,需要在hosts 配置
sudo vi /etc/hosts
把上面的代码输入上去 按 : wq 退出保存
然后在运行
最后 安装 cd my-nuxt. npm run dev
打开目录 vscode
vetur 把插件卸载 然后装上 Volar. vue3 的插件 这个是有好
在开发项目中我们经常会使用到一些第三方库/插件、组件来丰富项目,这里列举一些开发后台项目用到的第三方库/插件、组件。
naive-ui
安装 naive-ui
和 @css-render/vue3-ssr
yarn add -D naive-ui @css-render/vue3-ssr
成功之后再配置
在 nuxt.config.ts
增添下列配置
import { defineNuxtConfig } from 'nuxt' // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ build: { transpile: process.env.NODE_ENV === 'production' ? [ 'naive-ui', 'vueuc', '@css-render/vue3-ssr', '@juggle/resize-observer' ] : ['@juggle/resize-observer'] }, vite: { optimizeDeps: { include: process.env.NODE_ENV === 'development' ? ['naive-ui', 'vueuc', 'date-fns-tz/formatInTimeZone'] : [] } } })
然后再添加一个插件
新建目录: plugins - naive-ui.ts
import { setup } from '@css-render/vue3-ssr' import { defineNuxtPlugin } from '#app' export default defineNuxtPlugin((nuxtApp) => { if (process.server) { const { collect } = setup(nuxtApp.vueApp) const originalRenderMeta = nuxtApp.ssrContext?.renderMeta nuxtApp.ssrContext = nuxtApp.ssrContext || {} nuxtApp.ssrContext.renderMeta = () => { if (!originalRenderMeta) { return { headTags: collect() } } const originalMeta = originalRenderMeta() if ('then' in originalMeta) { return originalMeta.then((resolvedOriginalMeta) => { return { ...resolvedOriginalMeta, headTags: resolvedOriginalMeta['headTags'] + collect() } }) } else { return { ...originalMeta, headTags: originalMeta['headTags'] + collect() } } } } })
自定义错误页面
根目录下新建 error.vue
<template> <n-result status="500" title="错误提示" :description="error.message"> <template #footer> <n-button @click="handleError">回到首页</n-button> </template> </n-result> </template> <script setup> import { NResult, NButton } from "naive-ui" const prop = defineProps({ error : Object }) const handleError = () => clearError({ redirect: '/' }) </script> <style lang=""> </style>
在app.vue
<script setup> import { NButton } from "naive-ui" throwError('故意报错') </script>
就看到了这个页面
自定义全局loading 实现
上面那个不适合服务端渲染
然后要用到其他的UI的其他
然后再。plugins 下新建一个 globalloading.ts 文件
import { createDiscreteApi, ConfigProviderProps, // darkTheme, // lightTheme } from 'naive-ui' export default defineNuxtPlugin((nuxtApp) => { const bar = ref(null); nuxtApp.hook("app:mounted", (e) => { if (!bar.value) { const { loadingBar } = createDiscreteApi(['loadingBar']) bar.value = loadingBar; } console.log('app:mounted') }) nuxtApp.hook("page:start", (e) => { bar.value?.start() //bar.value ? bar.value.start() : ''; console.log('age:start') }) nuxtApp.hook("page:finish", (e) => { console.log('page:finish') // bar.value?.finish() setTimeout(() => { bar.value?.finish() }, 1000) }) nuxtApp.hook("app:error", (e) => { console.log('app;error') }) })
最后就看到了加载的页面进度条
安装 windicss
yarn add nuxt-windicss -D
关于NUXT目录的
Nuxt 使用.nuxt/目录在开发中生成您的Vue应用程序。
你不应该碰里面的任何文件,因为整个目录将在运行 nuxt dev 时重新创建。
就是最上面的nuxt 目录
components 组件
组件名策略
默认情况下,Nuxt自动导入components目录中的任何组件,组件名将基于它的路径、目录和文件名。
惰性加载组件
要动态导入一个组件(也称为惰性加载组件),则在组件名称前添加 Lazy
前缀。通过使用 Lazy
前缀,你可以延迟加载组件代码,直到合适的时刻
<template> <div> <MyImg /> <LazyMyImg /> </div> </template>
composables
composables 目录下的内容也将自动将 Vue 组合导入到应用中,Nuxt 只扫描 composables/ 目录的顶层文件。Composables 的主要作用是将常用逻辑和逻辑相关的代码抽象出来,以提高代码可复用性和可维护性,如:跨组件创建响应性的、对ssr友好的共享状态—— useState
。
/** * composables/counter.ts 内容 **/ export const userCounter = () => { return useState('counter', () => 0); };
/** * 业务组件 **/ <script setup> import Count from "~/components/business/Count.vue"; const counter = userCounter(); </script> <template> <div> 业务组件内容: {{ counter }} <a-button type="primary" @click="counter--"> - </a-button> <a-button type="primary" @click="counter++"> + </a-button> </div> </template>
/** * business/Count 组件内容 **/ <template> <div>Count组件内容:{{ counter }}</div> </template> <script setup lang="ts"> const counter = userCounter(); </script>
layouts
Nuxt提供了一个可定制的布局框架,可以在整个应用程序中使用,非常适合将常见的UI或代码模式提取到可重用的布局组件中。布局放在layouts/目录中,使用时将通过异步导入自动加载。
默认布局
在layouts目录下添加default.vue 布局文件。
不像其他组件,布局组件必须有一个根元素,以允许 Nuxt 在布局变化之间应用过渡-这个根元素不能是<slot />。如果你的应用只有一个布局,建议使用app.vue。
在布局文件中,布局的内容将加载在<slot />中,~/layouts/default.vue:
<template> <div> <slot /> </div> </template>
如果你使用app.vue你还需要添加 :
<template> <NuxtLayout> // 在app.vue中没有NuxtLayout组件,内容将会不显示 </NuxtLayout> </template>
配置布局
-| layouts/ ---| default.vue ---| custom.vue
可以在 NuxtLayout 中添加 name
属性来覆盖默认布局:
<template> <NuxtLayout :name="layout"> <NuxtPage /> </NuxtLayout> </template> <script setup> // 您可以根据API调用或登录状态来选择此选项 const layout = "custom"; </script>
也可以通过 definePageMeta
设置
<template> <NuxtLayout> 2023,12.23 </NuxtLayout> </template> <script setup> definePageMeta({ layout: "custom", }); </script>
如果业务组件不使用
<NuxtLayout>
组件包裹,配置布局是不会生效的
方法
pages 页面
页面目录。Nuxt 提供了一个基于文件的路由,使用 Vue Router 在底层创建路由。pages/index.vue 文件将被映射到应用程序 / 路由。
如果你正在使用app.vue,确保在 app.vue 使用 <NuxtPage/>
组件来显示当前页面。
动态路由
建立页面文件时,如果命名时将任何内容放在方括号内,它将被转换为路由参数。在文件名或目录中混合和匹配多个参数。
-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue
会生成路由:
{ "routes": [ { "name": "users-group-id", "path": "/users-:group()/:id()", "component": "~/pages/users-[group]/[id].vue" }, ] }
根据上面的例子,你可以通过 $route 对象中的 params 访问组件中的 group & idx
<template> <ul class="text-base text-gray-600"> <li> 获取到的 group 是 <span class="text-green-500 text-xl">{{ group }}</span> </li> <li> 获取到的 id 是 <span class="text-green-500 text-xl">{{ id }}</span> </li> </ul> </template>
nuxt3 中内置了四种请求的方法
useFetch
useLazyFetch
useAsyncData
useLazyAsyncData
推荐的话还是 useFetch
const param1 = ref('value1') const { data, pending, error, refresh } = await useFetch('/api/modules', { query: { param1, param2: 'value2' } })
Request 的封装与使用
没有用 nuxt3 自带的接口返回格式才进行这样的简易封装;
- ~enums/interface.ts // 定义接口返回 code 值的枚举
export enum ResultEnum { SUCCESS = 0, TOKEN_OVERDUE = 404, // 用户登录失败 INTERNAL_SERVER_ERROR = 500, // 服务异常 }
- ~/composables/useDefaultRequest.ts // 定义接口统一拦截函数
import { UseFetchOptions } from "nuxt/app"; import { RouteLocationRaw } from "vue-router"; import { ResultEnum } from "~/enums/interface"; interface DefaultResult<T = any> { code: number; data: T; msg: string; success: boolean; } type UrlType = string | Request | Ref<string | Request> | (() => string | Request); type HttpOption<T> = UseFetchOptions<DefaultResult<T>>; interface RequestConfig<T = any> extends HttpOption<T> { // 忽略拦截,不走拦截,拥有 responseData,且 code !== 0 的情况下,直接返回 responseData, // 但是若无 responseData, 不设置 ignoreGlobalErrorMessage 也会报错 ignoreCatch?: boolean; // 忽略全局错误提示,走拦截,但是任何情况下都不会提示错误信息 ignoreGlobalErrorMessage?: boolean; } const request = async <T>( url: UrlType, params: any, options: RequestConfig<T> ): Promise<DefaultResult<T> | T> => { const headers = useRequestHeaders(["cookie"]); const method = ((options?.method || "GET") as string).toUpperCase(); const runtimeConfig = useRuntimeConfig(); const nuxtApp = useNuxtApp(); const { $message, $login } = nuxtApp; const { apiBaseUrl } = runtimeConfig.public; const baseURL = `${apiBaseUrl}/mall/api`; // 处理用户信息过期 const hanlerTokenOverdue = async () => { const { _route } = nuxtApp; await $login(_route?.fullPath); }; // 处理报错异常 const handlerError = (msg = "服务异常") => { if (process.server) { showError({ message: msg, statusCode: 500 }); } else { $message.error(msg); } }; const { data, error } = await useFetch(url, { baseURL, headers, credentials: "include", params: method === "GET" ? params : undefined, body: method === "POST" ? JSON.stringify(params) : undefined, ...options, }); const responseData = data.value as DefaultResult<T>; const { ignoreCatch, ignoreGlobalErrorMessage } = options; // 忽略全局 if (error.value || !responseData) { if (!ignoreGlobalErrorMessage) handlerError(); return Promise.reject(error.value || "服务响应失败,请稍后重试"); } else { const { code, data: result, msg } = responseData; // 接口请求成功,直接返回结果 if (code === ResultEnum.SUCCESS || !code) { return result; } if (!ignoreCatch) { // 接口请求错误,统一处理 switch (code) { case ResultEnum.TOKEN_OVERDUE: // 登录信息过期,去登录 // 用户信息过期 await hanlerTokenOverdue(); default: if (!ignoreGlobalErrorMessage) handlerError(msg); return Promise.reject(msg || "服务响应失败,请稍后重试"); } } } return responseData; };