Skip to content

Latest commit

 

History

History
679 lines (524 loc) · 15.3 KB

05-生命周期-网络请求-服务器接口.md

File metadata and controls

679 lines (524 loc) · 15.3 KB

生命周期 & 网络请求 & 服务器接口

一、生命周期

客户端,会执行生命周期:

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 中监听。详见官方文档

1.App

通过插件的方式(推荐),监听:

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>

2.组件

页面也是组件:

mountedupdated 这样的生命周期钩子,不会在服务端被调用。

只有 beforeCreatecreated 这两个生命周期钩子,会在 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)

  • 本质和 useFetchlazy 属性设置为 true 一样。
  • 不会阻塞页面导航。

useLazyAsyncData(key, func)

  • 本质和 useAsyncDatalazy 属性设置为 true 一样。
  • 不会阻塞页面导航。

【注意】:这些函数,只能用在 setup or Lifecycle Hooks 中。

不能在 Options API 中使用。

🥚 案例理解:

1.$fetch

推荐使用 $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>

2.useAsyncData

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>

3.useFetch

useAsyncData Hook 的简写形式,

useFetch Hook,可指定请求相关的 options。

相比 useAsyncData Hook 不需要指定“唯一的 id“,会根据文件名和行号,自动生成。

详见官方文档。注意两个类型(type),UseFetchOptionsAsyncData。后续封装网络请求会用到:

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 的返回值,还可解构出 refreshpending ;

当请求的参数,是响应式的数据时,该参数发生改变,会自动重新发送网络请求。

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>

4.useLazyFetch

相比于 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 vs axios

官方推荐使用 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:请求配置选项。

    • 请求信息:methodquery(别名 params)、bodyheadersbaseURL
    • 请求拦截其:onRequestonResponselazy....
  • 返回值:一个对象,可解构出:{ data, pending, error, refresh }.

四、useFetch 的封装

封装 useFetch 步骤:

  1. 定义 ZtRequest 类,并导出这个类创建的实例.
  2. 在类中定义 requestgetpost 方法;
  3. request 中使用 useFetch 发起网络请求;
  4. 添加 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 接口,有如下步骤:

  1. server/api 目录下新建 homeinfo.ts
  2. 在该文件中使用 defineEventHandler 函数,来定义接口(支持 async);
  3. 然后在业务代码中,可用 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>

1.GET

如果要创建一个 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
    }
  }
})

2.POST

创建一个 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>