客户端,会执行生命周期:
Options API => Composition API
- beforeCreate => setup()
- created => setup()
- beforeMount => onBeforeMount
- mounted => onMounted
- beforeUpdate => onBeforeUpdate
- updated => onUpdated
- beforeDestroy => onBeforeUnmount
- destroyed => onUnmounted
- errorCaptured => onErrorCaptured
服务器端,会执行的生命周期:
Options API => Composition API
- beforeCreate => setup
- created
生命周期,只能在插件中监听,或者 Vue 的 composition API 中监听。详见官方文档。
通过插件的方式(推荐),监听:
在 plugins
目录下,创建一个插件,lifecycle.ts
文件(名称可随意取)。
demo-project\03-hello-nuxt\plugins\lifecycle.ts
export default defineNuxtPlugin((nuxtApp) => {
// Server & Client
nuxtApp.hook('app:created', (vuewApp) => {
console.log('app:created 执行了~')
})
// Client
nuxtApp.hook('app:beforeMount', (vuewApp) => {
console.log('app:beforeMoun 执行了~')
})
// Server & Client
nuxtApp.hook('vue:setup', () => {
console.log('vue:setup 执行了~')
})
// Server
nuxtApp.hook('app:rendered', (renderContext) => {
console.log('app:rendered 执行了~')
})
// Client
nuxtApp.hook('app:mounted', (vueApp) => {
console.log('app:mounted 执行了~')
})
})
在 .vue 文件中监听:
如果 setup 顶层写法的 .vue 文件中,监听生命周期,不能监听到 setup 以前的生命周期。
demo-project\03-hello-nuxt\app.vue
<script>
const nuxtApp = useNuxtApp()
// Server & Client 无法监听到
nuxtApp.hook('app:created', (vuewApp) => {
console.log('app:created 执行了~')
})
// Server & Client 无法监听到
nuxtApp.hook('vue:setup', () => {
console.log('vue:setup 执行了~')
})
// Client
nuxtApp.hook('app:beforeMount', (vuewApp) => {
console.log(`app:beforeMoun 执行了~`)
})
// Server
nuxtApp.hook('app:rendered', (renderContext) => {
console.log('app:rendered 执行了~')
})
// Client
nuxtApp.hook('app:mounted', (vueApp) => {
console.log('app:mounted 执行了~')
})
</script>
页面也是组件:
mounted
、updated
这样的生命周期钩子,不会在服务端被调用。
只有 beforeCreate
、created
这两个生命周期钩子,会在 SSR 期间(服务端)被调用。因此:
- 应避免在其中使用会产生副作用,且需要被清理的代码。
demo-project\03-hello-nuxt\pages\lifecycle.vue
<script>
export default {
name: 'lifecycle',
setup () {
console.log('setup~')
},
beforeCreate() {
console.log('beforeCreate~')
},
created() {
console.log('created~')
},
beforeMount() {
console.log('beforeMount~')
},
mounted() {
console.log('mounted~')
},
beforeUnmount() {
console.log('beforeUnmount~')
},
unmounted() {
console.log('unmounted~')
},
// 客户端,执行顺序
// setup~
// beforeCreate~
// created~
// beforeMount~
// mounted~
// 服务端,执行顺序:
// setup~
// beforeCreate~
// created~
}
</script>
在 Nuxt 中,网络请求,主要通过以下 4 个函数,来实现(支持 Server & Client):
$fetch(url, opts)
:
- 用于发起异步请求的全局函数(类似 Fetch API);
- 是一个跨平台请求库。
useAsyncData(key, func)
:
- 解决
$fetch
API 服务器,客户端,同时发送网络请求的问题,会阻塞页面导航。
useFetch(url, opts)
:
- 本质是
useAsyncData(key, () => $fetch(url, opts))
的语法糖。 - 会阻塞页面导航。
useLazyFetch(url, opts)
:
- 本质和
useFetch
的lazy
属性设置为true
一样。 - 不会阻塞页面导航。
useLazyAsyncData(key, func)
:
- 本质和
useAsyncData
的lazy
属性设置为true
一样。 - 不会阻塞页面导航。
【注意】:这些函数,只能用在
setup
or Lifecycle Hooks 中。不能在 Options API 中使用。
🥚 案例理解:
推荐使用 $fetch
发送请求。详见官方文档。
在 pages
目录下,创建 fetch.vue
文件。
demo-project\03-hello-nuxt\pages\fetch.vue
<script lang="ts" setup>
const BASE_RUL = 'http://codercba.com:9060/juanpi/api'
// 使用 $fetch 来发送网络请求.
// 服务端,客户端,都会发送,会发送两次
$fetch(BASE_RUL + '/homeInfo', {
method: 'GET',
}).then(res => {
console.log('res:', res)
})
</script>
useAsyncData
Hook 的使用。
在刷新页面时,客户端不会发送网络请求,服务端会;
进行路由跳转时,客户端会发送网络请求,服务端不会。
demo-project\03-hello-nuxt\pages\fetch.vue
<script lang="ts" setup>
const BASE_RUL = 'http://codercba.com:9060/juanpi/api'
interface IResultData {
code: number
data: any
}
// useAsyncData 在刷新页面时,可减少客户端发起的一次网络请求。
// 第一个参数,为一个唯一 id,通常是是请求 url
const { data } = await useAsyncData<IResultData>('homeinfo', () => {
return $fetch(BASE_RUL + '/homeInfo', { method: 'GET' })
})
console.log('res:', data.value?.data)
</script>
useAsyncData
Hook 的简写形式,
useFetch
Hook,可指定请求相关的 options。
相比 useAsyncData
Hook 不需要指定“唯一的 id“,会根据文件名和行号,自动生成。
详见官方文档。注意两个类型(type),
UseFetchOptions
、AsyncData
。后续封装网络请求会用到:
demo-project\03-hello-nuxt\pages\fetch.vue
<script lang="ts" setup>
const { data: newData } = await useFetch<IResultData>('/goods', {
method: 'POST',
baseURL: BASE_RUL,
body: {
count: 2
}
/* query: {
name: 'zzt'
}, */
// params: {}, // query 的别名
// headers: {}
})
console.log('newData:', newData.value?.data)
console.log('newData:', newData.value?.data)
</script>
请求的拦截器:
demo-project\03-hello-nuxt\pages\fetch.vue
<script setup lang="ts">
const { data: newData } = await useFetch<IResultData>('/goods', {
method: 'POST',
baseURL: BASE_RUL,
body: {
count: 2
},
// 请求的拦截其
onRequest({ request, options }) {
console.log('options:', options)
console.log('options.method:', options.method)
// 添加请求头。
options.headers = {
token: 'xxxx'
}
},
// 请求错误的拦截其
onRequestError({ request, options, error }) {
console.log('onRequestError~')
},
onResponse({ request, response, options }) {
console.log('onResponse~')
console.log('response._data:', response._data)
// return response._data // 不能返回到 data,除非重写它
response._data.data = {
name: 'zzt'
}
},
onResponseError({ request, response, options, error }) {
console.log('onResponseError~')
}
})
console.log('newData:', newData.value?.data)
console.log('newData:', newData.value?.data)
</script>
在客户端刷新页面。
useFetch
API 的返回值,还可解构出 refresh
、pending
;
当请求的参数,是响应式的数据时,该参数发生改变,会自动重新发送网络请求。
在 pages
目录下,创建 refresh.vue
页面。
demo-project\03-hello-nuxt\pages\refresh.vue
<script setup lang="ts">
const BASE_URL = "http://codercba.com:9060/juanpi/api";
interface IResultData {
code: number;
data: any;
}
const count = ref(1);
// 1.点击刷新时, 是在 server 端发起网络请求, 客户端不会发起网络请求,水合之后,客户端是可以获取到数据
const { data, refresh, pending } = await useFetch<IResultData>(
BASE_URL + "/goods",
{
method: "POST",
body: {
count,
},
}
);
console.log(data.value?.data);
function refreshPage() {
count.value++; // 会自动从新发起网络请求
// refresh(); // client 刷新,重新请求。
}
</script>
<template>
<div class="refresh">
refresh
{{ pending }}
<div>
<button @click="refreshPage">count++</button>
</div>
</div>
</template>
相比于 useFetch
API,默认不会阻塞页面的导航。
在 pages
目录下,新建 lazy.vue
页面。
demo-project\03-hello-nuxt\pages\lazy.vue
<script lang="ts" setup>
const BASE_RUL = 'http://codercba.com:9060/juanpi/api'
interface IResultData {
code: number
data: any
}
const { data } = await useFetch<IResultData>('/goods', {
method: 'POST',
baseURL: BASE_RUL,
body: {
count: 2
},
lazy: true // 不会阻塞页面的导航
})
console.log('data.value:', data?.value)
watch(data, (newVal) => {
console.log('data newVal:', newVal)
})
onMounted(() => {
console.log('lazy onmounted~')
})
</script>
简写形式:
使用 useLazyFetch
API。
demo-project\03-hello-nuxt\pages\lazy.vue
<script lang="ts" setup>
const BASE_RUL = 'http://codercba.com:9060/juanpi/api'
interface IResultData {
code: number
data: any
}
const { data } = await useLazyFetch<IResultData>('/goods', {
method: 'POST',
baseURL: BASE_RUL,
body: {
count: 2
},
})
watch(data, (newVal) => {
console.log('data newVal:', newVal)
})
onMounted(() => {
console.log('lazy onmounted~')
})
</script>
官方推荐使用 useFetch
函数,而不是 axios:
useFetch
底层调用的是 $fetch
函数,该函数基于 unjs/ohmyfetch 请求库封装,与原生的 Fetch API 相似:
unjs/ohmyfetch 是一个跨端请求库(A better fetch API. Works on node, browser and workers):会自动解析响应和对数据进行字符串化。
useFetch
支持智能的类型提示和智能的推断 API 响应类型。
setup
中用 useFetch
获取数据,会减去客户端重复发起的请求。
useFetch(url, options)
语法:
-
url
: 请求的路径。 -
options
:请求配置选项。- 请求信息:
method
、query
(别名params
)、body
、headers
、baseURL
- 请求拦截其:
onRequest
、onResponse
、lazy
....
- 请求信息:
-
返回值:一个对象,可解构出:
{ data, pending, error, refresh }
.
封装 useFetch
步骤:
- 定义
ZtRequest
类,并导出这个类创建的实例. - 在类中定义
request
、get
、post
方法; - 在
request
中使用useFetch
发起网络请求; - 添加 TypeScript 类型声明;
创建 service
目录,在其中创建 index.ts
;
demo-project\03-hello-nuxt\service\index.ts
import type { AsyncData, UseFetchOptions } from 'nuxt/app'
const BASE_URL = 'http://codercba.com:9060/juanpi/api'
type Method = 'GET' | 'POST'
class ZtRequest {
request<T = any>(
url: string,
method: Method,
data?: any,
options?: UseFetchOptions<T>
): Promise<AsyncData<T, Error>> {
return new Promise((resolve, reject) => {
const newOption: UseFetchOptions<T> = {
baseURL: BASE_URL,
method,
...options
}
if (method === 'GET') {
newOption.query = data
} else if (method === 'POST') {
newOption.body = data
}
useFetch<T>(url, newOption as any)
.then(res => {
resolve(res as AsyncData<T, Error>)
})
.catch(err => {
reject(err)
})
})
}
get<T = any>(url: string, param?: any, options?: UseFetchOptions<T>) {
return this.request(url, 'GET', param, options)
}
post<T = any>(url: string, data?: any, options?: UseFetchOptions<T>) {
return this.request(url, 'POST', data, options)
}
}
export interface IResultData<T> {
code: number,
data: T
}
export default new ZtRequest()
使用封装的网络请求。
demo-project\03-hello-nuxt\service\home.ts
import ztRequest from './index';
import type { IResultData } from './index';
export const fetchHomeInfoData = () => {
return ztRequest.get<IResultData<any>>('/homeInfo')
}
demo-project\03-hello-nuxt\pages\use-fetch.vue
<script lang="ts" setup>
import { fetchHomeInfoData } from '@/service/home';
const { data } = await fetchHomeInfoData()
console.log('use-fetch data:', data.value?.data)
</script>
Nuxt3 提供了编写后端服务接口的功能,在 server/api
目录下编写:
比如:编写一个 /api/homeinfo
接口,有如下步骤:
- 在
server/api
目录下新建homeinfo.ts
; - 在该文件中使用
defineEventHandler
函数,来定义接口(支持async
); - 然后在业务代码中,可用
useFetch
函数,发送网络请求,调用:/api/homeinfo
接口;
🥚 案例裂解:
在 server/api
目录下,创建 homeInfo.ts
。
demo-project\03-hello-nuxt\server\api\homeInfo.ts
// 默认同时支持 POST、GET 方法。
export default defineEventHandler((event) => {
return {
code: 200,
data: {
name: 'zzt',
age: 18
}
}
})
开启服务,在浏览器中,可直接通过 GET 请求,访问以下接口:
http://localhost:3000/api/homeInfo
在 .vue 文件中,访问该接口。
demo-project\03-hello-nuxt\pages\login.vue
<script lang="ts" setup>
const { data } = await useFetch('/api/homeInfo')
console.log('login data:', data.value?.data)
</script>
如果要创建一个 GET 方法呢?
在 server/api
目录下,创建 homeInfo.get.ts
文件。
同理,也支持 .post
,.delete
,.path
这样的后缀。
demo-project\03-hello-nuxt\server\api\homeInfo.get.ts
export default defineEventHandler((event) => {
const { req, res } = event.node;
console.log('req.method:', req.method)
console.log('req.url:', req.url)
return {
code: 200,
data: {
name: 'zzt',
age: 18
}
}
})
创建一个 POST 请求。
在其中拿到请求的 query,method,body...,使用一些全局的函数。
demo-project\03-hello-nuxt\server\api\login.post.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const method = getMethod(event)
const body = await readBody(event)
const bodyRaw = await readRawBody(event)
console.log('query:', query)
console.log('method:', method)
console.log('body:', body)
console.log('bodyRaw:', bodyRaw)
return {
code: 200,
data: {
name: 'zzt',
age: 18,
id: 16
}
}
})
在 login.vue
中,发送网络请求,将结果存在 cookie 中。
demo-project\03-hello-nuxt\pages\login.vue
<script lang="ts" setup>
async function onLoginrequestClick() {
const { data: loginData } = await useFetch('/api/login?id=100', {
method: 'POST',
body: {
username: 'admin',
password: '1234567'
}
})
console.log('logindata:', loginData.value?.data)
// Server & Client
const cookie = useCookie('token', {
maxAge: 60 // 1 分钟后过期
})
cookie.value = loginData.value?.data.token as string
return navigateTo('/') // 跳转到首页
}
</script>
<template>
<div>
Page: Login
</div>
<div>
<button @click="onLoginrequestClick">login request</button>
</div>
</template>
在首页,获取 cookie
demo-project\03-hello-nuxt\pages\index.vue
<script setup lang="ts">
const cookie = useCookie('token')
console.log('cookie value:', cookie.value)
</script>