前言
大家好,我是村长,下面这篇长文是我历时1个多月做的
Nuxt3
学习成果的输出,由于精力和水平有限,难免有错漏,欢迎大家指正。后续我还有继续更新该系列的计划和想法,希望大家多多点赞支持一下,谢谢各位朋友啦! 欢迎关注我的公众号「村长学前端」,新视频和文章会第一时间在公众号发送,另外大家可以输入“加群”和200+前端大咖讨论学习!
小右13号在微博给Nuxt 3
带货了:
23号的直播分享再一次给Nuxt 3
带货,这次独占一页PPT:
看来这个Nuxt3
一定不简单!那这个框架是做啥的呢?村长就带大家来一探究竟!
字面意思看是一款基于Vue3
的混合开发框架。
那什么是混合(Hybrid)开发呢?继续看官方介绍,Hybrid状态还是soon,表示增量的静态生成ISG以及一些其他可能的高级模式,静态生成Nuxt2就有,但是每次都全量更新整个网站肯定是不好的,这个增量更新真的值得期待!下面的很多新特性:整合vite + vue3 + composition api + ts、CLI、DevTools、Nuxt Kit表明,这是一个体系完备的通用开发框架,能提供良好的代码组织、极高的开发效率、开发体验和服务端渲染/静态网站生成(SSR/SSG)能力,这才是硬核!
配套视频
我专门录制了Nuxt3从入门到实战系列视频,爱看视频学习的小伙伴千万不要错过!
space.bilibili.com/480140591/c…
尝鲜Nuxt3,尤雨溪力荐的框架怎么样?
创建项目
打开 Visual Studio Code , 打开内置终端并输入下面命令创建一个nuxt
项目:
npx nuxi init nuxt3-app
踩坑指南:node版本需要高于v14.16.0
安装依赖
yarn install
启动
使用 yarn dev
以 开发模式启动nuxt:
yarn dev
✨浏览器会自动打开:http://localhost:3000
最小应用
在nuxt3中如果没有pages/
目录,则表示不会包含vue-router依赖。如果我们不需要路由或者就是一个落地页时就可以这么搞。
下面删除app.vue中<NuxtWelcome />
,随便添点内容看看效果:
<template>
<div>
<h1>nuxt3 app</h1>
</div>
</template>
下面我们加个页面试试,创建layouts/index.vue:
<template>
<div>index page</div>
</template>
别忘了添加路由出口,app.vue:
<template>
<div>
<h1>nuxt3 app</h1>
<!-- 路由出口 -->
<NuxtPage></NuxtPage>
</div>
</template>
那如果我有另一个页面detail.vue
想要跳转过去哪?
可以像下面这样,添加一个NuxtLink
,index.vue:
<template>
<div>index page</div>
<!--跳转链接-->
<NuxtLink to="/detail">Detail Page</NuxtLink>
</template>
现在可以自由的跳转了!
这个约定路由用起来可太方便了,但是大家需要知道很多规则才能用好,比如:
- 嵌套路由怎么搞?
- 动态路由怎么搞?
等等问题,下面让村长给大家一一道来!快给我点个赞吧,这会让我更新的更快!😊
约定路由,用起来可真爽啊!
前面写了最小nuxt3应用,我们试用了一下多页面写法,关于是否引入vue-router,nuxt3的行为是:如果只有app.vue不创建pages目录,将不会引入vue-router,则打包体积更小,反之则引入路由库,相当智能吧!
页面路径
nuxt3会自动整合vue-router,并且映射pages/
目录到应用的routes配置中。就像上一讲演示的index.vue
和detail.vue
,它们在最终生成的路由配置表中大概是下面这样:
[
{
path: '/',
component: '~/pages/index.vue',
name: 'index',
},
{
path: '/detail',
component: '~/pages/detail.vue',
name: 'detail',
}
]
动态路由
如果我们在文件名或者文件夹名称里面包含了方括号
,它们将被转换为动态路由
参数。
比如下面这样的文件结构:
-| pages/
---| users-[group]/
-----| [id].vue
上面案例我们可以在组件[id].vue
中访问group
、id
这两个参数:
<template>
{{ $route.params.group }}
{{ $route.params.id }}
</template>
通过 /users-admins/123
导航即可:
<NuxtLink to="/users-admins/123">管理员123</NuxtLink>
嵌套路由
目录和文件同名,就制造了嵌套路由。
比如下面目录结构:
-| pages/
---| parent/
------| child.vue
---| parent.vue
child.vue
<template>
<div>
<h1>child page</h1>
</div>
</template>
父组件中使用NuxtChild组件显示嵌套子组件内容,parent.vue:
<template>
<div>
<h1>parent page</h1>
<!-- 子组件出口 -->
<NuxtChild></NuxtChild>
</div>
</template>
试一下,index.vue
<NuxtLink to="/parent/child">Parent</NuxtLink>
产生的路由会像下面这样:
{
path: '/parent',
children: [
{
path: 'child'
}
]
}
那如果只使用/parent
会怎么样?发现内容没有了,显然需要一个{path: '/parent/'}
子路由
解决方法也很简单,在parent/
目录下加一个index.vue
即可。
原理
动态路由的原理可以简单在.nuxt
目录中一探究竟
有时候需要在不同页面间共用模板页:这就要用到模板功能,接下来将带大家一起看看怎么做到这一点。
写的头晕,大家快给村长点个赞吧,这会让我快速缓解😊!
巧用布局模板,高效开发从这里开始!
页面布局
前面我们试用了两个重要功能:动态路由
和嵌套路由
。
体验便捷的同时,当然也会有另一些重要需求,比如:别名、重定向和路由守卫等,我考查了以下v2中的可用方案:
- router-extras-module 在页面中自定义路由参数
- @nuxtjs/router 覆盖Nuxt路由并编写自己的
router.js
文件 - 在配置文件
nuxt.config.js
中使用router.extendRoutes 选项
经测试已经全部失效了!所以小伙伴们等以上两个扩展更新: )
接下来我们研究一下nuxt的布局系统,通过这个自定义的布局页,我们可以提取一些通用UI或代码到可重用的布局组件中,非常便捷,下面我们开始吧!
默认布局
那些放在layouts/
目录下的SFC会被自动加载进来,如果我们创建的SFC名为default.vue
,将会被用于项目所有页面中作为布局模板。
layouts/default.vue:
<template>
<div>
通用布局页,default.vue:
<slot />
</div>
</template>
效果如下:可见还是嵌套在app.vue中
自定义布局文件
如果我们的布局文件名不叫default,而是别的,比如custom.vue
,想要使用它们,就必须在某个页面中设置页面属性layout
。
custom.vue:
<template>
<div>
内容来自自定义布局页custom.vue!
<slot />
</div>
</template>
可以在helloworld.vue中试试custom这个布局,helloworld.vue:
<script>
export default {
layout: "custom"
}
</script>
试了一下,嵌套路由中是没有效果的,这可能是有意为之的
使用NuxtLayout
可以使用NuxtLayout组件结合slots获得完全控制力,同时需要设置组件选项layout: false
。
helloworld.vue
<template>
<NuxtLayout name="custom">
<template #header>
<h1>hello page</h1>
</template>
some content...
</NuxtLayout>
</template>
<script>
export default {
layout: false,
};
</script>
修改一下custom.vue
<template>
<div>
内容来自自定义布局页custom.vue!
<slot name="header"/>
<slot />
</div>
</template>
我们甚至能组合多个布局页:
<template>
<div>
<NuxtLayout name="custom">
<template #header>
<h1>hello page</h1>
</template>
some content...
</NuxtLayout>
<NuxtLayout name="default">
some content...
</NuxtLayout>
</div>
</template>
配合
由于需要设置layout选项,所以在这个script标签旁边同时使用
<script>
export default {
layout: "custom",
};
</script>
<script setup>
// your setup script
</script>
组件自动导入,用就完了!开发体验杠杠的
组件 Components
我估计大家应该挺烦每次使用组件时的各种导入和注册操作,这点nuxt中早就解决了,用就完了!
组件自动导入,开发体验杠杠的!
自动导入组件
我们把Vue组件放在components/
目录,这些组件可以被用在页面和其他组件中,以往我们使用这些组件需要导入并注册它们,但Nuxt会自动导入components/
目录中的任意组件。比如:
| components/
--| TheHeader.vue
--| TheFooter.vue
layouts/default.vue:
<template>
<div>
<TheHeader />
<slot />
<TheFooter />
</div>
</template>
组件名称约定
没有嵌套的组件会以文件名直接导入,但如果存在嵌套关系哪?例如下面的路径:
| components/
--| base/
----| foo/
------| Button.vue
那么组件名称将会基于路径和文件名连起来,比如上面的base/foo/Button.vue
注册名称将会是BaseFooButton
,将来用起来会像下面这样:
<BaseFooButton />
组件懒加载
如果在组件名前面加上Lazy前缀,则可以按需懒加载该组件,可用于优化打包尺寸。
比如,下面的用法:
<template>
<div>
<h1>Mountains</h1>
<LazyMountainsList v-if="show" />
<button v-if="!show" @click="show = true">显示列表</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
const show = ref(false)
</script>
前面都是nuxt3架构上解决的一些问题相关,下面我们开始演示nuxt3在业务编写方面的功能,比如数据获取、可复用业务逻辑、状态管理等等。写的手都酸了,大家给点点赞鼓励一下村长呗!
数据获取,就是这么轻松惬意
上一篇写了组件自动导入,通过nuxt
的组件系统,我们使用组件时不需要导入和注册组件,用就完了,开发体验杠杠的!但我突然想到,我们平时开发还需要用到第三方组件库,那么组件库能自动导入吗?
相关的文档参见:v3.nuxtjs.org/docs/direct…
所以如果严格按照nuxt
要求定义我们组件库并添加nuxt.js
文件,自动引入自然没有问题。然而在我尝试的两个组件库:vue-devui
和naive-ui
中,都不是很贴合它的要求,所以均以失败告终!希望有识之士可以折腾一下,如果你有解决方案,还请不吝赐教!
那么如何引入它们哪?其实通过插件体系获取
app
实例就能手动注册,所以这并不是大问题。
本篇我们写一写组件逻辑,首先想到的是获取数据。
nuxt3
中提供的数据获取函数有以下四个:
- useFetch
- useLazyFetch
- useAsyncData
- useLazyAsyncData
注意:它们都必须在
setup
或生命周期钩子
中使用
useAsyncData
在页面、组件或插件中都可以使用useAsyncData
获取那些异步数据。比如:
const {
data: Ref<DataT>, // 返回的数据
pending: Ref<boolean>, // 加载状态指示器
refresh: (force?: boolean) => Promise<void>, // 强制刷新函数
error?: any // 请求失败的错误信息
} = useAsyncData(
key: string,// 唯一键用于多次请求结果去重
fn: () => Object,// 返回数值的异步函数
// lazy - 是否在路由之后才请求数据,server - 是否在服务端请求数据
options?: { lazy: boolean, server: boolean }
)
获取待办事项数据,index.vue:
<template>
<div>
<!-- 待办列表 -->
<div v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.completed">
<strong>{{todo.title}}</strong>
</div>
</div>
</template>
<script setup lang="ts">
const { data: todos } = await useAsyncData(
'count', () => $fetch('/api/todos'))
</script>
$fetch使用参考ohmyfetch
useLazyAsyncData
该方法等效于useAsyncData
,仅仅设置了lazy
选项为true,也就是它不会阻塞路由导航,这意味着我们需要处理data为null的情况(或者通过default选购给data设置一个默认值)
useFetch
页面、组件或者插件中可以使用useFetch
获取任意URL资源。
useFetch
是对useAsyncData
包装,自动生成key同时推断响应类型,用起来更简单。
看下面方法签名,基本完全相同:
const {
data: Ref<DataT>,
pending: Ref<boolean>,
refresh: (force?: boolean) => Promise<void>,
error?: any
} = useFetch(url: string, options?)
useLazyFetch
该方法等效于useFetch
,仅设置了lazy
选项为true,所以它不会阻塞路由导航,这意味着我们需要处理data为null的情况(或者通过default选购给data设置一个默认值)
最佳实践
只选取需要的数据
由于请求回来的数据会存储在页面payload中,甚至包括那些没有用到的字段,所以文档中明确建议大家只选择那些组件用到的数据,我们可以设置$fetch的pick
选项。
比如,下面的用法:
const { data: mountain } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description']
})
创建SSR友好的跨组件共享状态
上一篇写了数据获取的各种姿势,最终大意了没有闪,以至于pick
部分举例欠妥:
const { data: todos } = await useFetch("/api/todo", {
pick: ["id", "title", "completed"],
});
导致了500
错误,这里需要说明的是,pick期望的数据结构应该是对象,而我提供的是数组:
const todos = [
{ id: 1, title: 'nuxt3', completed: false },
{ id: 2, title: 'vue3', completed: true },
]
export default () => {
return todos
}
所以如果修改为对象:
export default () => {
return {
code: 1,
data: todos
}
}
则可以正常pick
:
const { data: todos } = await useFetch("/api/todo", {
pick: ['data']
});
pick
的行为是,摘出来这些部分做个新对象:
// 型如 { data: todos }
// 所以我之前代码:const { data: todos } = ...
// todos并非是数组而是{ data: todos }
所以页面中使用也要对应:
<div v-for="todo in todos.data" :key="todo.id">
有人问如果有层级pick需求该当如何?我估计pick选项没这个能力。如果想要对数据做强力控制,还得是transform
:
const { data: todos } = await useFetch("/api/todo", {
transform(input) {
return input.data;
},
});
此时页面也可以直接使用todos
了!
状态共享
Nuxt3提供了 useState
创建响应式且服务端友好的跨组件状态共享能力。
useState
是服务端友好的ref
替换。它的值在服务端渲染(客户端注水的过程中)将被保留并通过唯一key在组件间共享。
方法签名
useState<T>(key: string, init?: () => T): Ref<T>
- key:唯一键用于去重
- init:提供初始值的函数
useState实践
声明一个状态,index.vue
const counter = useState("counter", () => Math.round(Math.random() * 1000))
<button @click="counter++">+</button>
{{ counter }}
<button @click="counter--">-</button>
共享状态
我们的全局状态当然想要在组件之间共享,此时可以利用nuxt的composables自动导入特性,把它们定义在composables目录中,这样他们将成为全局类型安全的状态。
composables/useCounter.ts
export const useCounter = () =>
useState("counter", () => Math.round(Math.random() * 1000));
配套视频
我专门录制了Nuxt3从入门到实战系列视频,爱看视频学习的小伙伴不要错过!
space.bilibili.com/480140591/c…
小伙们别忘了动动小手,三连一波鼓励一下村长啊!
有了数据和状态,下一步是进一步强化框架的能力,nuxt中有plugins
和modules
两个重要的扩展方式,下面我会带大家一起研究一下,手又酸了,大家点个关注心理按摩一下☺️~
巧用插件机制,强化Nuxt的利器!
上一篇写了nuxt状态共享,本篇我们研究nuxt3的插件系统。通过插件系统,我们可以获取nuxt实例以及vue实例,这样我们有机会扩展nuxt或vue,比如引入一个UI库。
plugins目录
Nuxt3会自动读取plugins目录下的文件并加载它们。我们可以在文件名上使用.server或者.client前缀使他们仅作用域服务端或者客户端。
创建插件
使用defineNuxtPlugin()注册一个插件:
import { defineNuxtPlugin } from '#app'
// 唯一的参数是nuxt实例
export default defineNuxtPlugin(nuxtApp => {
// Doing something with nuxtApp
})
插件用例:自动提供帮助方法
一个常见应用是给NuxtApp实例提供一些额外的帮助方法,我们可以通过编写一个插件,返回一个对象,在里面设置provide
key,比如:
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin(() => {
return {
provide: {
hello: () => 'world'
}
}
})
使用这个helper,index.vue:
// 会自动加上$前缀
const { $hello } = useNuxtApp();
console.log($hello())
插件用例:访问Vue实例
如果想要扩展Vue,我们通常要访问Vue实例,引入Vue插件。在nuxt3中可以通过插件访问nuxtApp.vueApp.use(xxx)
做到。
我们引入vue-devui
试一下,前面我们曾经试图自动导入失败了,这里我们手动导入:
npm i vue-devui
创建一个插件vue-devui-plugin.ts:
import { defineNuxtPlugin } from "#app";
import { Button } from "vue-devui";
import 'vue-devui/button/style.css'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Button);
});
使用组件测试一下:
<d-button>按钮</d-button>
扩展
引入其他组件库尝试结果:
- naive-ui按官方方式在SFC中直接使用组件 引入就报错,参见:github.com/TuSimple/na… 又找到了这个回答:github.com/TuSimple/na… 不过这个解决方案关闭了
vite
,不是我喜欢的风格,仅供大家可以参考!
import { NButton } from 'naive-ui'
<n-button>button</n-button>
- vant是可以的,不过暂时不知道样式如何按需引入,编写如下插件: 可以看一下有赞官方有计划做一个next3+vant的demo
import { defineNuxtPlugin } from "#app";
import { Button } from 'vant';
import 'vant/lib/index.css'
// 这里如果引入 vant/lib/button/index.css是没有效果的
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Button)
});
-
element-plus官方有个starter项目:
只可惜也是全量引入,按需引入没交代,也明确了自动引入暂时支持不了。
实在写不动了,大家三连鼓励一下村长吧~下次一定继续更新!
配套视频
我专门录制了Nuxt3从入门到实战系列视频,爱看视频学习的小伙伴千万不要错过!
space.bilibili.com/480140591/c…
下次预告
使用插件plugins
之后你会发现我们在这里需要写的内容很可能是会在下个项目中重复的,在nuxt中还有个概念叫模块modules
,下一次我们研究几个已存在模块的用法,感受一下这种更高效的模块复用方式!
关注村长
欢迎关注我的公众号**「村长学前端」**,第一时间推送原创新视频和文章,另外大家可以输入“加群”和200+前端大咖一起讨论学习!