Nuxt
模块安装
在官网的 modules 页面,搜索相关模块名,例如 pinia,点击进入详情后可以看到对应的安装命令。
pinia
官网安装命令
npx nuxi@latest module add pinia杂项
创建布局
nuxt 创建布局有快捷命令
pnpm nuxt add layout <布局名称>以 default 为例,执行后会在 app/layouts 目录下生成一个 default.vue 文件。
<NuxtLayout name="default"></NuxtLayout>页面组件路由
我们经常为某个页面单独创建一些组件,这些组件仅为了页面而用,并不应该被放置在 components 目录下。
例如这里有如下目录结构
├── layouts│ └── default│ ├── index.vue│ └── components│ ├── Header.vue│ └── Footer.vue└── pages └── index ├── index.vue └── components ├── HorizontalRail.vue └── MasonryGrid.vue在这个目录结构里,layouts 和 pages 目录下的 components 目录里的组件,我们称之为页面级组件,这种组件在 Nuxt 项目里面临两个痛点,我们需要一一解决一下:
自动注册页面级组件
Nuxt 默认只会为 components 目录下的组件提供自动注册功能,页面级组件需要我们自行导入才能使用,这显然比较麻烦,此时我们需要修改 nuxt.config.ts 的相关配置:
export default defineNuxtConfig( { // ... components: [ { path: "~/components", pathPrefix: true }, // 复制默认配置,避免被覆盖掉 { path: "~/layouts", pattern: "**/components/**/*.vue", pathPrefix: true }, { path: "~/pages", pattern: "**/components/**/*.vue", pathPrefix: true } ], // ...} );其中
pathPrefix的值为true时,组件的注册名称会包含文件路径
修改完成后,就可以删除掉手动导入代码,直接在页面里使用组件了(但组件名需要携带路径前缀)。
<DefaultComponentsHeader /><DefaultComponentsFooter />
<!-- app/pages/index/index.vue --><IndexComponentsHorizontalRail /><IndexComponentsMasonryGrid />避免页面级组件被注册为路由
由于 Nuxt 会自动将 app/pages 目录下的 Vue 文件注册为路由组件,此时让我们打开 Nuxt 开发者工具的 page 页面,就会发现里面出现了如下两项
//components/HorizontalRail//components/MasonryGrid此时我们甚至能通过 /components/HorizontalRail 来直接访问组件,显然这是我们不希望看到的。
此时同样需要修改 nuxt.config.ts 的相关配置,来屏蔽掉我们希望屏蔽的文件:
export default defineNuxtConfig( { // ... pages: { pattern: [ "**/*.vue", // 复制默认配置,避免被覆盖掉 "!**/components/**/*.vue" ] }, // ...} );修改后重启服务,可以看到开发者工具的 page 页面里已经没有了之前的组件了。
内置工具函数
definePageMeta
定义被 Nuxt 自动加载的页面(默认 app/pages 下面的内容)的元数据。
definePageMeta( { layout: ""} );介绍重点属性:
- layout: 指定页面使用的布局组件名称,若不想使用任意布局,则可以设置为
false。该设定仅会影响未指定name的 NuxtLayout
内置组件
NuxtLayout
组件有一个 name 属性,默认为 default(不严谨),用来指定布局名称。Nuxt 会在 app/layouts 目录下寻找对应名称的布局组件来渲染页面内容。
<NuxtLayout name="default"></NuxtLayout><!-- 简写 --><NuxtLayout></NuxtLayout>NuxtLayout 还可以接受一些附加 props,这些 props 将会被传递给布局组件,slot 插槽内容同理。
<NuxtLayout name="default" :foo="bar" :baz="qux"> <!-- 页面内容 --></NuxtLayout>自动分配 layout
上面说 NuxtLayout 的 name 默认值是 default 实际上是不严谨的,NuxtLayout 组件实际是遵循如下优先级尝试分配 layout:
- 是否指定了
name,如果已指定,使用name - 当前页面元数据 meta(参考definePageMeta) 里是否指定了
layout,如果已指定,使用layout - 前两者均未指定,使用
default
NuxtPage
NuxtPage 组件是 Nuxt 对 <RouterView> 的封装,同样支持附加 props 和 slot 插槽内容。
<NuxtPage :foo="bar" :baz="qux"> <!-- 页面内容 --></NuxtPage>内置组合式函数
useCookie
一个方便操作 cookie 的组合式函数,返回一个对应值的 Ref 对象
const token = useCookie( "token" )console.log( token.value );default
指定 ck 不存在时,返回默认值。值为一个函数,函数的返回值也可以是个 Ref 对象。类型如下:
default: () => T | Ref<T>示例:
const token = useCookie( "token", { default: () => "Bear 114514"} );默认开启 watch 的情况下,如果页面里不存在指定的 Cookie,将会直接把 default 值写入到浏览器的该 Cookie 里。
watch
watch 默认为 true。该监听主要起的作用为:当返回的 cookie ref 的 .value 发生变化时,是否自动写入到浏览器的 Cookie 里。
该属性开启时,default 属性的值也会在浏览器不存在对应 cookie 时,自动写入到浏览器。
下面这个案例,浏览器的 tk Cookie 在页面加载完毕后会变成 default,3 秒后会自动变成 updated(值更新需要手动点击浏览器 Application 页签下的刷新按钮才可观察到)
const tk = useCookie( "tk", { default: () => "default"} );
setTimeout( () => { tk.value = "updated";}, 3000 )而如果是浏览器端的更新,如后端设置 Cookie 导致的 Cookie 变化,或者用户手动修改 Cookie,此时 watch 依旧会自动监听:
const tk = useCookie( "tk", { default: () => "default"} );
watch( () => tk.value, newVal => { console.log( "tk changed:", newVal ); // 浏览器里手动修改 tk 值,自动在此处打印} );这主要是因为在 Nuxt v3.12.0 版本后,实验性的 cookieStore 选项被自动启用,该选项启用后浏览器的 Cookie 发生变化时,会自动刷新代码中的 Cookie 值。
我们在 nuxt.config.ts 配置文件中可以手动关闭该功能。
export default defineNuxtConfig( { // ... experimental: { cookieStore: false, }, // ...} );该选项关闭后,watch 便不再会自动监听浏览器 Cookie 的修改。
此时需要我们手动调用 refreshCookie 工具函数来刷新代码中的 cookie 值:
const token = useCookie( "token", { default: () => "default"} );
watch( () => token.value, newVal => { console.log( "token 更新:", newVal );} );
const login = async () => { await $fetch( "/api/token" ); // 后端讲 cooken 设置为 Bear 114514 refreshCookie( "token" );}上述案例,当 login 方法执行成功后,watch 会正常监听到 token 的变化,并打印出新的值 Bear 114514。
但需要注意的是,调用 refreshCookie 后并不会立刻刷新代码中 cookie 字面量的值,哪怕在 nextTick 后尝试打印也无法获取。
我们对上面的 login 方法进行一些改动,追加部分打印代码:
const login = async () => { await $fetch( "/api/token" ); console.log( "refresh 之前:", token.value ); refreshCookie( "token" ); nextTick(); console.log( "refresh 之后:", token.value );}当 login 方法执行成功后,控制台会依次打印出:
refresh 之前: defaultrefresh 之后: defaulttoken 更新: Bear 114514这是因为 refreshCookie 仅仅只是唤起更新 cookie 的这个异步行为,并不保证该行为会在何时完成。
useFetch
useFetch 是对 useAsyncData 和 $fetch 的封装,避免了单用 $fetch 会出现的 客户端+服务端 两次请求的情况,返回响应式的组合式函数。
是否使用 await
useFetch 和传统 promise 不同,对其使用 await 关键字并不会影响他的返回结果。
不论是否使用 await,他都会返回一个 _AsyncData 响应式对象,且几个属性的读取方式完全一样:
watchEffect( () => { console.log( "[useFetch]", { pending: dataFetch.pending.value, status: dataFetch.status?.value, error: dataFetch.error.value, data: dataFetch.data.value, } );} );那么使用与否 await 有什么区别呢?
当使用 await 时,useFetch 会阻塞 ssr 渲染,通俗理解也就是:先等待接口请求完成,再返回 html 内容给浏览器。对 SEO 更友好,但会增加首屏加载时间。
动态 query 参数
query 参数可以接收一个 computed 响应式对象,此时只要 computed 依赖的响应式数据发生变化,useFetch 就会重新发起请求,并更新调用 useFetch 得到的响应式对象(下面例子中的 fetchRes)。
const fetchRes = await useFetch<{ data: FeedItem[]; total: number, hasMore: boolean }>( "/api/feed", { key: `feed:main-${ currentPage.value }`, query: computed( () => ( { page: currentPage.value, pageSize: pageSize.value, seed: "main" } ) )} );有时我们并不希望 computed 对象发生变化时自动请求,更偏向于人为通过 refresh 方法来控制请求的发起,此时我们可以在 useFetch 的 options 里设置 watch: false 来禁止自动请求。
const fetchRes = await useFetch<{ data: FeedItem[]; total: number, hasMore: boolean }>( "/api/feed", { key: `feed:main-${ currentPage.value }`, query: computed( () => ( { page: currentPage.value, pageSize: pageSize.value, seed: "main" } ) ), watch: false} );
function pageChange( page: number ) { currentPage.value = page; fetchRes.refresh();}错误处理
当请求出现错误(500/前端代码错误)时,fetchRes.error.value 将会返回一个包含错误信息的对象,且此时 fetchRes.data.value 的值为 undefined。
需要注意的是,页面并不会像传统前端那样在控制台出现红色错误提示,因此当请求数据莫名其妙不渲染时,请检查一下是否存在请求错误。
Nuxt-UI
通用配置
初启动字体报错
使用 Nuxt UI 模板后,如果人在国内,初次启动会出现如下报错
Could not fetch from https://fonts.google.com/metadata/fonts. Will retry in 1000ms.这是因为 Nuxt UI 模板默认使用了 Google Fonts 来加载字体,而 Google Fonts 在国内访问可能会受到限制,导致无法正常加载字体资源。
此时我们安装一个 @nuxt/fonts 模块,可以在官方文档 - Modules 里搜索 font。
# nuxt module add 命令执行后,会自动在 `nuxt.config.ts` 文件里添加模块使用的相关配置npx nuxt module add fonts这个模块可以定制化的处理字体优化和加载,此时我们可以通过把文件改为从本地读取的方式来暂时屏蔽这个报错。
此来前往 nuxt.config.ts 文件里找到 fonts 配置项,修改为如下内容:
export default defineNuxtConfig({ // ... fonts: { provider: "local" }, // ...})本地读取除了会从系统字体读取外,还会自动从
public目录里读取资源。
不论哪种方式,读取字体类型均参照 assets/css/main.css 文件里 --font-sans 的定义,一般来说,文件默认内容应该是长这样:
@theme static { --font-sans: "Public Sans", sans-serif;}这个字体语句就不做解释了,前端人都懂什么意思。
优化字体加载方式(如何加载外部css)
使用 local 本地加载的方式存在一种问题,即用户系统内若没有安装对应字体,就会导致字体渲染失败。
固然可以通过将字体文件放在 public 目录下的方式来解决这个问题,但这会增加项目的体积(尤其是中文字体文件)延缓加载速度,并且需要手动管理字体文件。
对此,我们使用线上字体资源加载的方式来解决这个问题,这里再次使用 Google Fonts 官网(没办法这个字体网站确实最好用),小小的记录一下这个网站的使用方式:
- 左侧可以对字体进行筛选
- 找到中意的字体后,点击进入字体详情页
- 右上角有个
Get font按钮,点击后即可以加入到购物车 - 点击右上角的购物车图标进入购物车页面,点击
Get embed code按钮,即可以得到加载这个字体的代码
我们选择了中英文两个字体,然后通过上面几步,得到了这样一段代码:
中文字体加载英文通常并不会很好看,而英文字体基本都不会支持中文字符,因此需要引入两个字体
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Playwrite+NZ+Basic:[email protected]&family=ZCOOL+KuaiLe&display=swap" rel="stylesheet">而 Nuxt 项目是没有 index.html 文件的,所以我们需要修改 nuxt.config.ts 文件的 app.head 配置项:
export default defineNuxtConfig( { // ... app: { head: { link: [ { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Playwrite+NZ+Basic:[email protected]&family=ZCOOL+KuaiLe&display=swap" } ] } }, // ...} );然后修改 assets/css/main.css 文件里 --font-sans 的定义,添加我们新加入的字体:
@theme static { --font-sans: "Playwrite New Zealand Basic", "ZCOOL KuaiLe", "Public Sans", sans-serif;}这里的字体定义顺序是有讲究的,因为英文字体通常不支持中文字符,所以我们把英文字体放在最前面。这样在加载字体时,由于字体不支持,会退而求其次选择第二个中文字体。
解决了外部字体加载问题后,我们需要再解决一下字体加载超时问题,这里可以通过两种方式:
- cdn
- oss 平台托管(在 Google Fonts 购物车界面,选择
Download all,下载后再上传获取在线链接)
第二种存在费用问题,这里我们介绍第一种方式,以 https://www.webcache.cn/#fonts 举例:
首先查看我们的原链接 xxx/css2?xxx,因此我们在页面上找到 css2 页签,然后按照指引操作,即将 font.webcache.cn/google 替换掉 fonts.googleapis.com。
export default defineNuxtConfig( { // ... app: { head: { link: [ { rel: "stylesheet", href: "https://font.webcache.cn/google/css2?family=Playwrite+NZ+Basic:[email protected]&family=ZCOOL+KuaiLe&display=swap" } ] } } // ...} );主题修改
在官网的右上角放大镜左边,有一个绿色的小按钮,点击后可以切换主题。修改包括但不限于主题色、默认图标库、圆角等样式。
修改后在下方的 Export 栏,可以选择复制 main.css 或 app.config.ts 代码,分别粘贴到项目的 app/assets/css/main.css 和 app/app.config.ts 文件里即可。
主题色类名是 xxx-primary,如文字主题色是
text-primary
组件相关
Nuxt UI 的组件是以 U 开头的,而 Nuxt 官方的组件是以 Nuxt 开头的。
UIcon
在项目启动时,会有一行打印日志
✔ Nuxt Icon discovered local-installed 2 collections: lucide, simple-icons可以看到当前使用的图标库为 lucide 和 simple-icons
在 <UIcon> 组件上使用图标时,name 的指定格式需要固定为: i-[图标库名称]-[图标名称],以 lucide 的 battery-full 图标举例:
<UIcon name="i-lucide-battery-full" />如何自行添加新的图标库
NuxtUI 是使用 iconify[https://icon-sets.iconify.design] 来管理图标的,我们可以任意添加 iconify 支持的图标库。
首先我们可以在 iconify 上随便找一个图标使用,点击图标后在下方切换到 component 页签,复制名称,在 <UIcon 组件上使用,以 material-symbols:10k 为例:
<UIcon name="i-material-symbols-10k" />此时页面图标生效,但控制台给出了两行提示:
WARN [Icon] Collection material-symbols is not found locally 15:27:39We suggest to install it via npm i -D @iconify-json/material-symbols to provide the best end-user experience.即此时的所谓生效,其实本质是采用了远程拉取的模式,Nuxt 建议我们最好将这个图标所存在的图标库(即 material-symbols)安装到本地,我们来安装一下
pnpm add -D @iconify-json/material-symbols安装完成后,我们再次启动项目,可以看到在日志中,我们新安装的图标库被成功识别了:
✔ Nuxt Icon discovered local-installed 3 collections: lucide, material-symbols, simple-icons我们也可以不用等他提示警告再安装,只需要遇到想要的图标库时,直接执行如下命令安装即可:
pnpm add -D @iconify-json/[图标库名称]如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时