挺早之前就想写个 api 接口服务,封装下自己收集的一些 api 接口,以便调用,正好最近在接触 SSR 框架,所以就使用 Nuxt3 来编写该项目。
在线地址: API-Service
开源地址: kuizuo/api-service
如果你已经了解过 Nuxt3 与运行过程,那么可以直接跳转至 实战
npx nuxi init nuxt3-app
可能会安装不上 会提示 could not fetch remote https://github.com/nuxt/starter
,大概率就是本地电脑无法访问 github,这时候科学上网都不一定好使,这里建议尝试更换下网络或设置 host 文件。
安装完毕后,根据提示安装依赖与启动项目
初始的 nuxt3 项目及其简单,甚至没有 page、components、assets 等目录。
关于 nuxt3 本文不做过多介绍,本文只对部分进行介绍。nuxt 已经发布快 1 年了,相信外面很多相关介绍文章。
Nuxt3 介绍
自动导入
nuxt.js 与 next.js 极其相像,但 nuxt 却精简许多,这归功于 nuxt 的自动导入,这可以让你无需导入像 vue 中的 ref 等等函数,导入组件等操作,不过前提是代码文件位置要符合 nuxt 规范。如果你尝试使用过 vite 的一些自动导入插件,其效果是一样的,只不过 nuxt 都已经配置好,开箱即用。
文件路由
pages 为 nuxt 中页面所存放的位置,会将 pages 目录下的文件(.vue
, .js
, .jsx
, .ts
or .tsx
) 与路由映射,像pages/index.vue
映射为 /
,然后在 app.vue 中通过<NuxtPage/>
来展示 pages。
要注意,pages 下的文件一定要有根节点,不然在路由切换的时候可能会出现问题(事实上建议所以的 vue 组件都有根节点,虽说 vue3 允许多个根节点,但或多或少存在一定问题)
至于动态路由与嵌套路由,文档说明的比较详细了,这里就不费口舌了
服务引擎
Nuxt3 中的 的 api 接口服务引擎使用的是⚗️ Nitro 的 JavaScript 服务,使用的是h3的 http 框架(相当于 hook 版的 http 框架),不过文档不是特别详细,很多东西都要琢磨。(这个框架是真的相对冷门,之前都未曾听闻过)
关于 Nuxt3 的服务具体可以看 Nuxt 3 - Server Routes,这里演示部分代码
创建一个服务,创建文件server/api/hello.ts
export default defineEventHandler(event => {
return 'hello nuxt'
})
请求 http://localhost/api/hello 便可得到hello nuxt
,在 event 可以得到 req 与 res 对象。不过在 req 身上是获取不到 query 和 body 的,这里需要使用 h3 提供的 hooks,如useMethod()
,useQuery()
,useBody()
来获取,例如。
export default eventHandler(async event => {
const body = await useBody(event)
return `User updated!`
})
这与传统的 node 的 http 框架不同点就是 query,body 这些参数不是从函数的上下文(context)取,而是通过 hook 来获取,所以这就是我为什么我说这相当于 hook 版的框架。关于这些 api,可以点我查看
数据获取
定义完了接口,那必然是要获取数据的,nuxt.js 有四种方式来获取数据,不过主要就二种useFetch
与useAsyncData
,另外两种是其懒加载形式。
像上面定义了 helloworld 接口就可以像下面这样使用
<script setup>
const { data } = await useFetch('/api/hello')
console.log(data) // hello nuxt
</script>
<template>
{{ data }}
</template>
useAsyncData
<script setup>
const { data } = await useAsyncData('hello', () => $fetch('/api/hello'))
console.log(data) // hello nuxt
</script>
<template>
{{ data }}
</template>
至于 useAsyncData 与 useFetch 有什么区别的话,如果请求的是 url 资源,那么建议使用 useFetch,如果请求的是其他来源的资源,就使用 useAsyncData。可以说在请求 url 资源时,两者是等价的,如下
useFetch(url) <==> useAsyncData(url, () => $fetch(url))
那么如何 SSR(服务端渲染)呢? nuxt3
默认是全 SSR
的渲染模式,也就是说在上面的数据请求后就是 SSR 渲染,客户端接受到的也就是带有数据页面。
如果要使用传统的客户端渲染只需要填加一个 options 的 server 参数为 false 即可,如
const { data } = await useFetch('/api/hello', { server: false })
自己尝试下将 server 切换,然后打开控制台->网络中查看 Fetch/XHR 中是否有和数据相关的请求便可知道是在服 务端发送的请求数据,还是客户端发送的数据。
实战
模板
这个项目所使用的模板是 Vitesse for Nuxt 3
该模板中集成了一些 vue 生态的相关模块(vueuse, pinia, unocss),开发者可以不必自行封装这些模块。
页面设计
页面设计的话其实没啥好说的,主要使用到了原子类的一个框架unocss。
接口转发
这里我会以通过每日一言的 api 例子来给你演示其功能实现,请求该 api 可以得到
{
"id": 5233,
"uuid": "9504a2a2-bab7-4c7d-b643-a6642ed5c55e",
"hitokoto": "人间没有单纯的快乐,快乐总夹带着烦恼和忧虑。",
"type": "d",
"from": "杨绛",
"from_who": "我们仨",
"creator": "a632079",
"creator_uid": 1044,
"reviewer": 4756,
"commit_from": "web",
"created_at": "1583786494",
"length": 22
}
这里创建server/api/one.ts
文件
export default defineEventHandler(async (event) => {
const { type = 'text' } = useQuery(event)
const data = await (await fetch('https://v1.hitokoto.cn/')).json()
if (type = 'json') {
return data
}
else {
event.res.setHeader('Content-Type', 'text/html;charset=utf-8')
return data.hitokoto
}
}
这样,这个接口就已经定义完毕了,此时访问 /api/one 所得到的就是一句短语。默认状态下返回文本,如需要 json 数据等额外信息,则可添加type=json
。例请求/api/one?type=json
,得到的完整数据如下
{
"id": 7173,
"uuid": "49eff9ca-7145-4c5f-8e62-d3dca63537fa",
"hitokoto": "即使人生是一场悲剧,也应该笑着把人生演完。",
"type": "k",
"from": "查拉图斯特如是说",
"from_who": "尼采",
"creator": "Kyanite",
"creator_uid": 8042,
"reviewer": 1,
"commit_from": "web",
"created_at": "1614946509",
"length": 21
}
而这整个过程也就是其实也就是接口转发,将访问 /api/one
的请求转发给目标 url https://v1.hitokoto.cn/ 的过程,然后对其数据进行抽取和封装,最终展示给调用方。
然而这只是完成了接口的转发,那么接口的文档又该如何实现呢?