Nuxt3学习记录

Nuxt3学习记录

安装环境

新项目

先决条件

终端

npx create-nuxt-app proOne

yarn create nuxt-app proOne

pnpm dlx nuxi@latest init

https://codeload.github.com/nuxt/starter/tar.gz/refs/heads/v3

然后

yarn install

npm install

pnpm install --shamefully-hoist

自动导入

Nuxt自动导入组件、组合式函数、辅助函数和Vue API,无需显式导入即可在整个应用程序中使用。

<script setup lang="ts">
const count = ref(1) // ref会自动导入
</script>

由于Nuxt具有固定的目录结构,它可以自动导入components/composables/utils/

Nuxt会自动导入函数和组合式函数,以执行数据获取、访问应用上下文运行时配置、管理状态或定义组件和插件。

<script setup lang="ts">
/* useAsyncData()和$fetch()会自动导入 */
const { data, refresh, pending } = await useFetch('/api/hello')
</script>

Vue 3提供了响应性API,如refcomputed,以及生命周期钩子和助手函数,这些都会被Nuxt自动导入。

<script setup lang="ts">
/* ref()和computed()会自动导入 */
const count = ref(1)
const double = computed(() => count.value * 2)
</script>

显示导入

Nuxt使用#imports别名来公开每个自动导入的内容,如果需要,可以使用它来使导入变得显式。

<script setup lang="ts">
import { ref, computed } from '#imports'

const count = ref(1)
const double = computed(() => count.value * 2)
</script>

禁止自动导入

export default defineNuxtConfig({
  imports: {
    autoImport: false
  }
})

样式化

可以编写自己的样式,或者引用本地和外部样式表。 可以使用 CSS 预处理器、CSS 框架、UI 库和 Nuxt 模块。

本地样式表

如果你正在编写本地样式表,将它们放在 assets/ 目录 是最自然的位置。

在组件中引入

你可以在页面、布局和组件中直接引入样式表。 你可以使用 JavaScript 的 import,或者使用 css 的 @import 语句

<script>
// 使用静态导入以实现服务器端兼容性
import '~/assets/css/first.css'

// 注意:动态导入不兼容服务器端
import('~/assets/css/first.css')
</script>

<style>
@import url("~/assets/css/second.css");
</style>

样式表将被内联到 Nuxt 渲染的 HTML 中。

CSS 属性

export default defineNuxtConfig({
  css: ['~/assets/css/main.css']
})

样式表将被内联到 Nuxt 渲染的 HTML 中,全局注入并存在于所有页面中。

使用字体

将本地字体文件放在 ~/public/ 目录中,例如 ~/public/fonts。然后可以在样式表中使用 url() 引用它们。

@font-face {
  font-family: 'FarAwayGalaxy';
  src: url('/fonts/FarAwayGalaxy.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

然后在样式表、页面或组件中按名称引用你的字体:

<style>
h1 {
  font-family: 'FarAwayGalaxy', sans-serif;
}
</style>

通过 NPM 分发样式表

npm install animate.css

然后你可以直接在页面、布局和组件中引用它:

<script>
import 'animate.css'
</script>

<style>
@import url("animate.css");
</style>
<script>
import 'animate.css'
</script>

<style>
@import url("animate.css");
</style>

你还可以将该包作为字符串引用到 Nuxt 配置的 css 属性中。

export default defineNuxtConfig({
  css: ['animate.css']
})

使用预处理器

要使用 SCSS、Sass、Less 或 Stylus 等预处理器,首先要安装它。

npm install sass
npm install less
npm install stylus

编写样式表的自然位置是 assets 目录。 然后,你可以使用你的预处理器的语法在你的 app.vue(或布局文件)中导入你的源文件。

<style lang="scss">
@use "~/assets/scss/main.scss";
</style>

或者,你可以使用 Nuxt 配置的 css 属性。

export default defineNuxtConfig({
  css: ['~/assets/scss/main.scss']
})

单文件组件(SFC)样式化

Vue 和 SFC 的最大优点之一就是在处理样式方面非常好用。你可以直接在组件文件的样式块中编写 CSS 或预处理器代码,因此你可以拥有出色的开发体验,而无需使用 CSS-in-JS 等工具。

使用 v-bind 动态样式

你可以在样式块中使用 v-bind 函数引用 JavaScript 变量和表达式。 绑定将是动态的,这意味着如果变量值发生变化,样式将会更新。

<script setup lang="ts">
const color = ref("red")
</script>

<template>
  <div class="text">hello</div>
</template>

<style>
.text {
  color: v-bind(color);
}
</style>

作用域样式

scoped 属性允许你将样式应用于组件内部。使用此属性声明的样式将只适用于该组件。

<template>
  <div class="example">hi</div>
</template>

<style scoped>
.example {
  color: red;
}
</style>

CSS 模块

你可以使用 module 属性和 CSS Modules。可以使用注入的 $style 变量访问它。

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

预处理器支持

SFC 样式块支持预处理器语法。Vite 内置支持 .scss、.sass、.less、.styl 和 .stylus 文件,无需配置。你只需要首先安装它们,然后在 SFC 中使用 lang 属性直接引用它们。

<style lang="scss">
  /* 在这里编写 scss */
</style>

默认情况下,Nuxt 已经预配置了以下插件:

SEO和Meta

组件

Nuxt提供了<Title><Base><NoScript><Style><Meta><Link><Body><Html><Head>组件,让你可以直接在组件的模板中与元数据进行交互。

由于这些组件名称与原生HTML元素相匹配,在模板中将它们大写非常重要。

<Head><Body> 可以接受嵌套的元标签(出于美观的原因),但这对最终HTML中嵌套的元标签的渲染位置没有影响。

<script setup lang="ts">
const title = ref('你好,世界')
</script>

<template>
  <div>
    <Head>
      <Title>{{ title }}</Title>
      <Meta name="description" :content="title" />
      <Style type="text/css" children="body { background-color: green; }" />
    </Head>

    <h1>{{ title }}</h1>
  </div>
</template>

功能

响应式

所有属性都支持响应式,包括计算属性、getter 和响应式。

建议使用 getter (() => value) 而不是计算属性 (computed(() => value))。

<script setup lang="ts">
const description = ref('我的神奇网站。')

useHead({
  meta: [
    { name: 'description', content: description }
  ],
})
</script>
<script setup lang="ts">
const description = ref('我的神奇网站。')

useSeoMeta({
  description
})
</script>
<script setup lang="ts">
const description = ref('我的神奇网站。')
</script>

<template>
  <div>
    <Meta name="description" :content="description" />
  </div>
</template>
标题模板

你可以使用 titleTemplate 选项来提供一个动态模板,以自定义站点的标题,例如在每个页面的标题中添加站点名称。

titleTemplate 可以是一个字符串,其中 %s 会被标题替换,也可以是一个函数。

如果你想使用一个函数(以获得更多的控制),那么它不能在 nuxt.config 中设置,而是建议在 app.vue 文件中设置,这样它将适用于你站点上的所有页面:

<script setup lang="ts">
useHead({
  titleTemplate: (titleChunk) => {
    return titleChunk ? `${titleChunk} - 网站名称` : '网站名称';
  }
})
</script>

动态标题

在下面的示例中,titleTemplate可以被设置为带有%s占位符的字符串,也可以被设置为一个函数,这样可以更灵活地为你的Nuxt应用的每个路由动态设置页面标题:

<script setup lang="ts">
useHead({
  // 作为字符串,
  // 其中`%s`会被标题替换
  titleTemplate: '%s - 网站标题',
  // ... 或者作为一个函数
  titleTemplate: (productCategory) => {
    return productCategory
      ? `${productCategory} - 网站标题`
      : '网站标题'
  }
})
</script>

nuxt.config也可以用作设置页面标题的替代方法。然而,nuxt.config不允许页面标题是动态的。因此,建议在app.vue文件中使用titleTemplate来添加动态标题,然后应用于你的Nuxt应用的所有路由。

过渡效果

页面过渡

你可以启用页面过渡来为所有的页面应用自动过渡效果。

export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' }
  },
})

如果你同时更改布局和页面,这里设置的页面过渡效果将不会运行。相反,你应该设置布局过渡效果

要开始在页面之间添加过渡效果,请在你的app.vue文件中添加以下CSS:

<template>
  <NuxtPage />
</template>

<style>
.page-enter-active,
.page-leave-active {
  transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
  opacity: 0;
  filter: blur(1rem);
}
</style>
<template>
  <div>
    <h1>首页</h1>
    <NuxtLink to="/about">关于页面</NuxtLink>
  </div>
</template>
<template>
  <div>
    <h1>关于页面</h1>
    <NuxtLink to="/">首页</NuxtLink>
  </div>
</template>

要为页面设置不同的过渡效果,请在页面的definePageMeta中设置pageTransition键:

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'rotate'
  }
})
</script>
<template>
  <NuxtPage />
</template>

<style>
/* ... */
.rotate-enter-active,
.rotate-leave-active {
  transition: all 0.4s;
}
.rotate-enter-from,
.rotate-leave-to {
  opacity: 0;
  transform: rotate3d(1, 1, 1, 15deg);
}
</style>

切换到关于页面时,将添加3D旋转效果

布局过渡

启用布局过渡来为所有的布局应用自动过渡效果。

export default defineNuxtConfig({
  app: {
    layoutTransition: { name: 'layout', mode: 'out-in' }
  },
})
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

<style>
.layout-enter-active,
.layout-leave-active {
  transition: all 0.4s;
}
.layout-enter-from,
.layout-leave-to {
  filter: grayscale(1);
}
</style>
<template>
  <div>
    <pre>默认布局</pre>
    <slot />
  </div>
</template>

<style scoped>
div {
  background-color: lightgreen;
}
</style>
<template>
  <div>
    <pre>橙色布局</pre>
    <slot />
  </div>
</template>

<style scoped>
div {
  background-color: #eebb90;
  padding: 20px;
  height: 100vh;
}
</style>
<script setup lang="ts">
definePageMeta({
  layout: 'orange'
})
</script>

<template>
  <div>
    <h1>关于页面</h1>
    <NuxtLink to="/">首页</NuxtLink>
  </div>
</template>

全局设置

你可以使用nuxt.config全局自定义这些默认的过渡效果名称。

pageTransitionlayoutTransition键都接受TransitionProps作为JSON可序列化的值,你可以通过它传递自定义CSS过渡的namemode和其他有效的过渡属性。

export default defineNuxtConfig({
  app: {
    pageTransition: {
      name: 'fade',
      mode: 'out-in' // 默认值
    },
    layoutTransition: {
      name: 'slide',
      mode: 'out-in' // 默认值
    }
  }
})

要覆盖全局过渡属性,使用definePageMeta为单个Nuxt页面定义页面或布局过渡,并覆盖在nuxt.config文件中全局定义的任何页面或布局过渡。

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'bounce',
    mode: 'out-in' // 默认值
  }
})
</script>

禁用过渡效果

<script setup lang="ts">
definePageMeta({
  pageTransition: false,
  layoutTransition: false
})
</script>

或在nuxt.config中全局禁用:

defineNuxtConfig({
  app: {
    pageTransition: false,
    layoutTransition: false
  }
})

JavaScript钩子

对于高级用例,你可以使用JavaScript钩子为Nuxt页面创建高度动态和自定义的过渡效果。

这种方式非常适合使用GSAP等JavaScript动画库。

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'custom-flip',
    mode: 'out-in',
    onBeforeEnter: (el) => {
      console.log('进入之前...')
    },
    onEnter: (el, done) => {},
    onAfterEnter: (el) => {}
  }
})
</script>

动态过渡效果

要使用条件逻辑应用动态过渡效果,你可以利用内联middleware将不同的过渡名称分配给to.meta.pageTransition

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'slide-right',
    mode: 'out-in'
  },
  middleware (to, from) {
    to.meta.pageTransition.name = +to.params.id > +from.params.id ? 'slide-left' : 'slide-right'
  }
})
</script>

<template>
  <h1>#{{ $route.params.id }}</h1>
</template>

<style>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
  transition: all 0.2s;
}
.slide-left-enter-from {
  opacity: 0;
  transform: translate(50px, 0);
}
.slide-left-leave-to {
  opacity: 0;
  transform: translate(-50px, 0);
}
.slide-right-enter-from {
  opacity: 0;
  transform: translate(-50px, 0);
}
.slide-right-leave-to {
  opacity: 0;
  transform: translate(50px, 0);
}
</style>
<script setup lang="ts">
const route = useRoute()
const id = computed(() => Number(route.params.id || 1))
const prev = computed(() => '/' + (id.value - 1))
const next = computed(() => '/' + (id.value + 1))
</script>

<template>
  <div>
    <slot />
    <div v-if="$route.params.id">
      <NuxtLink :to="prev">⬅️</NuxtLink> |
      <NuxtLink :to="next">➡️</NuxtLink>
    </div>
  </div>
</template>

使用NuxtPage的过渡效果

当在app.vue中使用<NuxtPage />时,可以直接将过渡属性作为组件属性传递以激活全局过渡效果。

<template>
  <div>
    <NuxtLayout>
      <NuxtPage :transition="{
        name: 'bounce',
        mode: 'out-in'
      }" />
    </NuxtLayout>
  </div>
</template>

请记住,此页面过渡效果无法通过在单个页面上使用definePageMeta来覆盖。

数据获取

Nuxt 提供了两个组合函数和一个内置库,用于在浏览器或服务器环境中执行数据获取:useFetchuseAsyncData$fetch

简而言之:

  • useFetch 是在组件设置函数中处理数据获取的最简单方法。
  • $fetch 可以根据用户交互进行网络请求。
  • useAsyncData 结合 $fetch,提供了更精细的控制。

useFetchuseAsyncData 共享一组常见的选项和模式

网络重复请求

useFetchuseAsyncData 组合函数确保一旦在服务器上进行了 API 调用,数据将以有效的方式在负载中传递到客户端。

负载是通过 useNuxtApp().payload 访问的 JavaScript 对象。它在客户端上用于避免在浏览器中执行代码时重新获取相同的数据。

Suspense

Nuxt 在底层使用 Vue 的 组件,以防止在视图中的每个异步数据可用之前导航。数据获取组合函数可以帮助您利用此功能,并根据每个调用的需求使用最适合的方法。

useFetch

<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>

<template>
  页面访问量:{{ count }}
</template>

这个组合函数是 useAsyncData 组合函数和 $fetch 工具的封装。

$fetch

Nuxt 包括了 ofetch 库,并且作为全局别名 $fetch 自动导入到应用程序中。它是 useFetch 在幕后使用的工具。

const users = await $fetch('/api/users').catch((error) => error.data)

请注意,仅使用 $fetch 将不会提供 网络请求重复和导航阻止。建议在提交数据到事件处理程序时使用 $fetch,在客户端逻辑中使用,或与 useAsyncData 结合使用。

ofetch 库是基于 Fetch API 构建的,并为其添加了便利功能:

  • 在浏览器、Node 或 worker 环境中的使用方式相同
  • 自动解析响应
  • 错误处理
  • 自动重试
  • 拦截器

useAsyncData

useAsyncData 组合函数负责封装异步逻辑并在解析完成后返回结果。

事实上,useFetch(url) 几乎等同于 useAsyncData(url, () => $fetch(url)) - 它是为最常见的用例提供的开发者体验糖。

useAsyncData 的第一个参数是用于缓存第二个参数(查询函数)的响应的唯一键。如果直接传递查询函数,则可以忽略该参数。在这种情况下,它将自动生成。

const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

由于自动生成的键仅考虑调用 useAsyncData 的文件和行,因此建议始终创建自己的键以避免不需要的行为,如果您正在创建自己的自定义组合函数并封装 useAsyncData

const id = ref(1)

const { data, error } = await useAsyncData(`user:${id.value}`, () => {
  return myGetFunction('users', { id: id.value })
})

useAsyncData 组合函数是包装和等待多个 useFetch 完成,并获取每个结果的绝佳方式。

const { data: discounts, pending } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([$fetch('/cart/coupons'), $fetch('/cart/offers')])

  return {
    coupons,
    offers
  }
})

选项

懒加载

默认情况下,数据获取的组合函数会在异步函数解析完成之前使用Vue的Suspense进行页面导航。通过使用lazy选项,可以忽略此功能在客户端导航时的使用。在这种情况下,你需要手动处理加载状态,使用pending值。

<script setup lang="ts">
const { pending, data: posts } = useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- 你需要处理加载状态 -->
  <div v-if="pending">
    加载中...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- 做一些操作 -->
    </div>
  </div>
</template>
减少有效负载大小

pick选项可帮助你通过仅选择你想要从组合函数返回的字段来减少存储在HTML文档中的有效负载大小。

<script setup lang="ts">
/* 仅选择模板中使用的字段 */
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

如果需要更多的控制或映射多个对象,可以使用transform函数来修改查询结果。

const { data: mountains } = await useFetch('/api/mountains', { 
  transform: (mountains) => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  }
})

picktransform都不会阻止初始时获取不需要的数据。但它们将阻止不需要的数据被添加到从服务器传输到客户端的有效负载中。

缓存和重新获取数据

useFetchuseAsyncData使用键来防止重新获取相同的数据。

  • useFetch使用提供的URL作为键。或者,可以在作为最后一个参数传递的options对象中提供key值。
  • useAsyncData如果第一个参数是字符串,则将其用作键。如果第一个参数是执行查询的处理函数,则会为useAsyncData的实例生成一个基于文件名和行号的唯一键。

要根据键获取缓存的数据,可以使用useNuxtData

刷新和执行

如果要手动获取或刷新数据,请使用组合函数提供的executerefresh函数。

<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="refresh">刷新数据</button>
  </div>
</template>

execute函数是refresh的别名,使用方式完全相同,但在非立即的情况下更语义化。

监听

如果希望在应用程序中的其他响应式值更改时重新运行获取函数,请使用watch选项。可以将其用于一个或多个可监听的元素。

const id = ref(1)

const { data, error, refresh } = await useFetch('/api/users', {
  /* 更改id将触发重新获取 */
  watch: [id]
})

请注意,监视响应式值不会更改获取的URL。例如,这将保持获取用户的相同初始ID,因为URL是在调用函数时构建的。

const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id]
})

如果需要基于响应式值更改URL,可以使用计算URL

计算URL

有时,您可能需要从响应式值计算URL,并在每次更改时刷新数据。不需要费力地解决此问题,您可以将每个参数作为响应式值附加。Nuxt将自动使用响应式值并在每次更改时重新获取。

const id = ref(null)

const { data, pending } = useLazyFetch('/api/user', {
  query: {
    user_id: id
  }
})
不立即执行

useFetch 组合函数在调用时会立即开始获取数据。你可以通过设置 immediate: false 来阻止立即执行,例如,等待用户交互。

为此,你需要使用 status 来处理获取生命周期,并使用 execute 来开始数据获取。

<script setup lang="ts">
const { data, error, execute, pending, status } = await useLazyFetch('/api/comments')
</script>

<template>
  <div v-if="status === 'idle'">
    <button @click="execute">获取数据</button>
  </div>

  <div v-else-if="pending">
    加载评论中...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>

为了更精细地控制,status 变量可以有以下取值:

  • idle:获取未开始
  • pending:获取已开始但尚未完成
  • error:获取失败
  • success:获取成功完成
传递请求头和Cookie

当我们在浏览器中调用 $fetch 时,用户的请求头(如 cookie)会直接发送到 API。但在服务器端渲染期间,由于 $fetch 请求在服务器内部进行,它不包含用户浏览器的 Cookie,也不会传递来自获取响应的 Cookie。

Vue.js开发

自动导入

在 Nuxt 项目的 components/ 目录中创建的每个 Vue 组件都可以在你的项目中使用,无需导入。如果一个组件在任何地方都没有被使用,你的生产代码中也不会包含它。

Vue Router

大多数应用程序需要多个页面以及在它们之间导航的方式。这被称为路由。Nuxt 使用 pages/ 目录和命名约定,直接根据你的文件创建与之映射的路由,使用官方的 Vue Router 库

渲染模式

通用渲染

当浏览器请求启用了通用(服务器端+客户端)渲染的URL时,服务器将一个完整渲染的HTML页面返回给浏览器。无论页面是预先生成并缓存的,还是即时渲染的,在某个时刻,Nuxt都在服务器环境中运行了JavaScript(Vue.js)代码,生成了一个HTML文档。

为了不失去客户端渲染方法的好处,如动态界面和页面过渡效果,客户端(浏览器)在下载完HTML文档后会在后台加载运行在服务器上的JavaScript代码。浏览器再次解释它(因此称为通用渲染),Vue.js接管文档并启用交互性。

在浏览器中使静态页面具有交互性被称为“水合”。通用渲染使Nuxt应用程序能够快速加载页面,同时保留了客户端渲染的好处。

服务器端渲染的好处:

性能:用户可以立即访问页面的内容,因为浏览器可以比JavaScript生成的内容更快地显示静态内容。同时,当水合过程发生时,Nuxt保留了Web应用程序的交互性。

搜索引擎优化:通用渲染将整个HTML内容作为经典服务器应用程序直接传递给浏览器。Web爬虫可以直接索引页面的内容,这使得通用渲染成为任何希望快速索引的内容的绝佳选择。

服务器端渲染的缺点:

开发约束:服务器和浏览器环境提供的API不同,编写可以在两个环境中无缝运行的代码可能会有些棘手。幸运的是,Nuxt提供了指导方针和特定的变量,帮助你确定代码在哪个环境中执行。

成本:为了即时渲染页面,服务器需要运行。这和任何传统服务器一样增加了每月的成本。然而,由于浏览器在客户端导航时接管了服务器调用,调用次数大大减少。通过利用边缘端渲染,可以实现成本的降低。

客户端渲染

传统的Vue.js应用程序默认在浏览器(或客户端)中进行渲染。然后,Vue.js在浏览器下载和解析所有包含创建当前界面指令的JavaScript代码后,生成HTML元素。

客户端渲染的优点:

  • 开发速度:在完全在客户端进行工作时,我们不必担心代码的服务器兼容性,例如,使用仅在浏览器中可用的API,如window对象。

  • 成本较低:运行服务器会增加基础设施成本,因为需要在支持JavaScript的平台上运行。我们可以将仅客户端的应用程序托管在任何具有HTML、CSS和JavaScript文件的静态服务器上。

  • 离线工作:因为代码完全在浏览器中运行,所以在网络不可用的情况下,它可以继续正常工作。

客户端渲染的缺点:

  • 性能:用户必须等待浏览器下载、解析和运行JavaScript文件。根据下载部分的网络和解析和执行的用户设备的性能,这可能需要一些时间,并影响用户的体验。
  • 搜索引擎优化:通过客户端渲染提供的内容进行索引和更新比使用服务器渲染的HTML文档需要更长的时间。这与我们讨论的性能缺点有关,因为搜索引擎爬虫不会等待界面在第一次尝试索引页面时完全渲染。纯客户端渲染将导致内容在搜索结果页面中显示和更新所需的时间更长。

混合渲染

混合渲染允许每个路由使用路由规则的不同缓存规则,并决定服务器如何响应给定URL的新请求。

Nuxt 3包括了路由规则和混合渲染支持。使用路由规则,你可以为一组Nuxt路由定义规则,改变渲染模式或基于路由分配缓存策略!

Nuxt服务器将自动注册相应的中间件,并使用Nitro缓存层包装路由。

export default defineNuxtConfig({
  routeRules: {
    // 主页在构建时预渲染
    '/': { prerender: true },
    // 产品页面按需生成,后台自动重新验证
    '/products/**': { swr: 3600 },
    // 博客文章按需生成,直到下一次部署前持续有效
    '/blog/**': { isr: true },
    // 管理仪表板仅在客户端渲染
    '/admin/**': { ssr: false },
    // 在API路由上添加cors头
    '/api/**': { cors: true },
    // 跳转旧的URL
    '/old-page': { redirect: '/new-page' }
  }
})

路由规则

你可以使用以下不同的属性:

  • redirect: string - 定义服务器端重定向。
  • ssr: boolean - 禁用应用程序的服务器端渲染部分,使其仅支持SPA,使用ssr: false
  • cors: boolean - 使用cors: true自动添加CORS头部,你可以通过覆盖headers来自定义输出。
  • headers: object - 为你的站点的某些部分添加特定的头部,例如你的资源文件。
  • swr: number|boolean - 为服务器响应添加缓存头部,并在服务器或反向代理上缓存它,以配置的TTL(存活时间)进行缓存。Nitro的node-server预设能够缓存完整的响应。当TTL过期时,将发送缓存的响应,同时在后台重新生成页面。如果使用true,则添加了一个不带MaxAge的stale-while-revalidate头部。
  • isr: number|boolean - 行为与swr相同,除了我们能够将响应添加到支持此功能的CDN缓存中(目前支持Netlify或Vercel)。如果使用true,内容将在CDN中持久存在,直到下一次部署。
  • prerender:boolean - 在构建时预渲染路由,并将其包含在你的构建中作为静态资源。
  • experimentalNoScripts: boolean - 禁用Nuxt脚本的渲染和JS资源提示,用于你站点的某些部分。

服务器引擎

在开发Nuxt 3时,我们创建了一款新的服务器引擎:Nitro

它具备以下特性:

  • 跨平台支持,可用于Node.js、浏览器、service-workers等。
  • 原生支持无服务器架构。
  • 支持API路由。
  • 自动代码拆分和异步加载模块。
  • 静态+无服务器站点的混合模式。
  • 开发服务器支持热模块重载。

ES模块

CommonJS模块

CommonJS(CJS)是由Node.js引入的一种格式,允许在独立的JavaScript模块之间共享功能(了解更多)。 你可能已经熟悉这种语法:

const a = require('./a')

module.exports.a = a

像webpack和Rollup这样的打包工具支持这种语法,并允许你在浏览器中使用CommonJS编写的模块。

ESM语法

大多数情况下,当人们谈论ESM与CJS时,他们谈论的是一种不同的用于编写模块的语法

import a from './a'

export { a }

什么是’原生’ESM?

当使用你要安装到你的包中的模块时,情况会有所不同。一个示例库可能会暴露出CJS和ESM版本,并让我们选择使用哪个:

{
  "name": "sample-library",
  "main": "dist/sample-library.cjs.js",
  "module": "dist/sample-library.esm.js"
}

因此,在Nuxt 2中,打包工具(webpack)将为服务器构建引入CJS文件(‘main’),并为客户端构建使用ESM文件(‘module’)。

然而,在最新的Node.js LTS版本中,现在可以在Node.js中使用原生ESM模块。这意味着Node.js本身可以使用ESM语法处理JavaScript,尽管默认情况下并不这样做。启用ESM语法的两种最常见的方法是:

在你的package.json中设置type: 'module'并继续使用.js扩展名

使用.mjs文件扩展名(推荐)

这就是我们在Nuxt Nitro中所做的;我们输出一个.output/server/index.mjs文件。这告诉Node.js将此文件视为原生ES模块。

在Node.js上下文中什么是有效的导入?

Node支持以下类型的导入(参见文档):

  1. .mjs结尾的文件 - 这些文件应该使用ESM语法
  2. .cjs结尾的文件 - 这些文件应该使用CJS语法
  3. .js结尾的文件 - 这些文件应该使用CJS语法,除非它们的package.json中有type: 'module'

Components

Nuxt 会自动导入该目录中的所有组件(以及您可能使用的任何模块注册的组件)。

| components/
--| AppHeader.vue
--| AppFooter.vue

<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

组件名称

| components/
--| base/
----| foo/
------| Button.vue

那么引用就是这样的:

<BaseFooButton />

为了清晰起见,我们建议组件的文件名与其名称相匹配。因此,在上面的示例中,您可以将 Button.vue 重命名为 BaseFooButton.vue

如果您希望仅根据组件名称而不是路径自动导入组件,则需要使用扩展形式的配置对象将 pathPrefix 选项设置为 false

export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
+     pathPrefix: false,
    },
  ],
});

这将使用与 Nuxt 2 中使用的相同策略注册组件。例如,~/components/Some/MyComponent.vue 将可用作 <MyComponent> 而不是 <SomeMyComponent>

动态组件

如果您想使用 Vue 的 <component :is="someComputedComponent"> 语法,您需要使用 Vue 提供的 resolveComponent 辅助函数,或直接从 #components 导入组件并将其传递给 is 属性。

<script setup lang="ts">
import { SomeComponent } from '#components'

const MyButton = resolveComponent('MyButton')
</script>

<template>
  <component :is="clickable ? MyButton : 'div'" />
  <component :is="SomeComponent" />
</template>

动态导入

要动态导入组件(也称为延迟加载组件),您只需将 Lazy 前缀添加到组件的名称中。如果组件不总是需要,这特别有用。

通过使用 Lazy 前缀,您可以将组件代码的加载延迟到正确的时机,这有助于优化 JavaScript 包的大小。

<script setup>
const show = ref(false)
</script>

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
  </div>
</template>

直接导入

<script setup lang="ts">
import { NuxtLink, LazyMountainsList } from '#components'

const show = ref(false)
</script>

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
    <NuxtLink to="/">Home</NuxtLink>
  </div>
</template>

自定义目录

export default defineNuxtConfig({
  components: [
    // ~/calendar-module/components/event/Update.vue => <EventUpdate />
    { path: '~/calendar-module/components' },

    // ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
    { path: '~/user-module/components', pathPrefix: false },

    // ~/components/special-components/Btn.vue => <SpecialBtn />
    { path: '~/components/special-components', prefix: 'Special' },

    // 如果您想要覆盖 `~/components` 的子目录,请确保它是最后一个。
    //
    // ~/components/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components'
  ]
})

客户端组件

| components/
--| Comments.client.vue

<template>
  <div>
    <!-- 此组件仅在客户端渲染 -->
    <Comments />
  </div>
</template>

此功能仅适用于 Nuxt 的自动导入和 #components 导入。从实际路径显式导入这些组件不会将它们转换为仅客户端组件。

.client 组件仅在挂载后才会渲染。要在 onMounted() 的回调中访问渲染的模板,请在 onMounted() 钩子的回调中添加 await nextTick()

Composables

使用composables/目录将你的Vue组合式函数自动导入到你的应用程序中。

使用方法

方法1: 使用命名导出

export const useFoo = () => {
  return useState('foo', () => 'bar')
}

方法2: 使用默认导出

// 它将作为 useFoo() 可用(文件名的驼峰形式,不包括扩展名)
export default function () {
  return useState('foo', () => 'bar')
}

使用方法: 现在你可以在 .js.ts.vue 文件中使用自动导入的组合函数

<script setup lang="ts">
const foo = useFoo()
</script>

<template>
  <div>
    {{ foo }}
  </div>
</template>

嵌套组合函数

你可以在另一个组合函数中使用自动导入的组合函数:

export const useFoo = () => {
  const nuxtApp = useNuxtApp()
  const bar = useBar()
}

访问插件注入

export const useHello = () => {
  const nuxtApp = useNuxtApp()
  return nuxtApp.$hello
}

文件扫描方式

Nuxt 仅扫描位于 composables/ 目录的顶级文件,例如:

| composables/
---| index.ts     // 被扫描
---| useFoo.ts    // 被扫描
-----| nested/
-------| utils.ts // 不被扫描

只有 composables/index.tscomposables/useFoo.ts 会被搜索导入。

要使嵌套模块的自动导入工作,你可以重新导出它们(推荐)或配置扫描器以包括嵌套目录:

示例:composables/index.ts 文件中重新导出你需要的组合函数:

// 启用对此导出的自动导入
export { utils } from './nested/utils.ts'

示例: 扫描 composables/ 文件夹中的嵌套目录:

export default defineNuxtConfig({
  imports: {
    dirs: [
      // 扫描顶级模块
      'composables',
      // ... 或扫描带有特定名称和文件扩展名的一级嵌套模块
      'composables/*/index.{ts,js,mjs,mts}',
      // ... 或扫描给定目录中的所有模块
      'composables/**'
    ]
  }
})

pages

Nuxt 提供了基于文件的路由功能,用于在你的 Web 应用中创建路由。

为了减小应用程序的包大小,此目录是可选的,这意味着如果你只使用 app.vuevue-router 将不会被包含在内。要强制使用页面系统,请在 nuxt.config 中设置 pages: true,或者在 app/router.options.ts 中设置。

使用方法

页面是 Vue 组件,可以使用 Nuxt 支持的任何有效扩展名(默认为 .vue.js.jsx.mjs.ts.tsx)。

Nuxt 会自动为 ~/pages/ 目录中的每个页面创建一个路由。

<template>
  <h1>首页</h1>
</template>

如果你正在使用 app.vue,请确保使用 `` 组件来显示当前页面:

<template>
  <div>
    <!-- 所有页面共享的标记,例如导航栏 -->
    <NuxtPage />
  </div>
</template>

动态路由

如果你在方括号中放置任何内容,它将被转换为动态路由参数。你可以在文件名或目录中混合和匹配多个参数,甚至可以包含非动态文本。

如果你想要一个参数是可选的,你必须将其放在双方括号中 - 例如,~/pages/[[slug]]/index.vue~/pages/[[slug]].vue 将同时匹配 //test

-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue

在上面的示例中,你可以通过 $route 对象在组件中访问 group/id:

<template>
  <p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>

导航到 /users-admins/123 将渲染:

<p>admins - 123</p>

如果你想要使用 Composition API 访问路由,可以使用全局的 useRoute 函数,它允许你像 Options API 中的 this.$route 一样访问路由。

<script setup lang="ts">
const route = useRoute()

if (route.params.group === 'admins' && !route.params.id) {
  console.log('警告!确保用户已经通过身份验证!')
}
</script>

命名的父级路由将优先于嵌套的动态路由。对于 /foo/hello 路由,~/pages/foo.vue 将优先于 ~/pages/foo/[slug].vue。:br 使用 ~/pages/foo/index.vue~/pages/foo/[slug].vue 来匹配不同页面的 /foo/foo/hello

嵌套路由

你可以使用 <NuxtPage> 显示嵌套路由

-| pages/
---| parent/
------| child.vue
---| parent.vue

这个文件树将生成以下路由:

[
  {
    path: '/parent',
    component: '~/pages/parent.vue',
    name: 'parent',
    children: [
      {
        path: 'child',
        component: '~/pages/parent/child.vue',
        name: 'parent-child'
      }
    ]
  }
]

要显示 child.vue 组件,你需要在 pages/parent.vue 中插入 <NuxtPage> 组件:

<template>
  <div>
    <h1>我是父视图</h1>
    <NuxtPage :foobar="123" />
  </div>
</template>

子路由键

<template>
  <div>
    <h1>我是父视图</h1>
    <NuxtPage :page-key="route => route.fullPath" />
  </div>
</template>

或者:

<script setup lang="ts">
definePageMeta({
  key: route => route.fullPath
})
</script>

Layouts

Nuxt 提供了一个布局框架,用于将常见的 UI 模式提取为可重用的布局。

为了获得最佳性能,在使用时,放置在此目录中的组件将通过异步导入自动加载。

启用布局

通过在 app.vue 中添加 ``,可以启用布局:

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

使用布局时:

  • z在页面中使用 definePageMeta 设置 layout 属性。
  • 设置 <NuxtLayout>name 属性。

布局名称会被规范化为 kebab-case,因此 someLayout 将变为 some-layout

如果没有指定布局,将使用 layouts/default.vue

如果应用程序中只有

一个布局,建议使用 app.vue

与其他组件不同,布局必须具有单个根元素,以允许 Nuxt 在布局更改时应用过渡效果 - 而且此根元素不能是 <slot />

默认布局

添加 ~/layouts/default.vue

<template>
  <div>
    <p>一些在所有页面之间共享的默认布局内容</p>
    <slot />
  </div>
</template>

在布局文件中,页面的内容将显示在 <slot /> 组件中。

命名布局

-| layouts/
---| default.vue
---| custom.vue

然后你可以在页面中使用 custom 布局:

<script setup lang="ts">
definePageMeta({
  layout: 'custom'
})
</script>

你可以直接通过 ``name 属性覆盖所有页面的默认布局:

<script setup lang="ts">
// 可以基于 API 调用或登录状态进行选择
const layout = "custom";
</script>

<template>
  <NuxtLayout :name="layout">
    <NuxtPage />
  </NuxtLayout>
</template>

动态更改布局

你还可以使用 setPageLayout 辅助函数动态更改布局:

<script setup lang="ts">
function enableCustomLayout () {
  setPageLayout('custom')
}
definePageMeta({
  layout: false,
});
</script>

<template>
  <div>
    <button @click="enableCustomLayout">更新布局</button>
  </div>
</template>

在每个页面上覆盖布局

如果你使用页面,可以通过设置 layout: false,然后在页面内部使用 <NuxtLayout> 组件来完全控制布局。

<script setup lang="ts">
definePageMeta({
  layout: false,
})
</script>

<template>
  <div>
    <NuxtLayout name="custom">
      <template #header> 一些页眉模板内容。 </template>

      页面的其余部分
    </NuxtLayout>
  </div>
</template>

<template>
  <div>
    <header>
      <slot name="header">
        默认页眉内容
      </slot>
    </header>
    <main>
      <slot />
    </main>
  </div>
</template>

如果在页面中使用 <NuxtLayout>,请确保它不是根元素(或者禁用布局/页面过渡效果)。

utils

使用utils/目录在整个应用程序中自动导入你的工具函数。

使用方法

方法1:使用命名导出

export const { format: formatNumber } = Intl.NumberFormat('en-GB', {
  notation: 'compact',
  maximumFractionDigits: 1
})

方法2:使用默认导出

// 它将作为randomEntry()可用(文件名的驼峰形式,不包括扩展名)
export default function (arr: Array<any>) {
  return arr[Math.floor(Math.random() * arr.length)]
}

现在你可以在.js.ts.vue文件中使用自动导入的工具函数了。

<template>
  <p>{{ formatNumber(1234) }}</p>
</template>

utils/自动导入的工作方式和扫描方式与composables/目录相同。

server

server/目录用于在应用程序中注册API和服务器处理程序。

-| server/
---| api/
-----| hello.ts      # /api/hello
---| routes/
-----| bonjour.ts    # /bonjour
---| middleware/
-----| log.ts        # 记录所有请求

每个文件应该导出一个使用defineEventHandler()eventHandler()(别名)定义的默认函数。

处理程序可以直接返回JSON数据、Promise,或使用event.node.res.end()发送响应。

export default defineEventHandler((event) => {
  return {
    hello: 'world'
  }
})

现在你可以在你的页面和组件中普遍调用这个API:

<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>

<template>
  <pre>{{ data }}</pre>
</template>

服务端路由

~/server/api目录中的文件会自动在其路由中添加/api前缀。

要添加没有/api前缀的服务器路由,请将它们放入~/server/routes目录中。

export default defineEventHandler(() => 'Hello World!')

服务器中间件

Nuxt会自动读取~/server/middleware目录中的任何文件,以创建项目的服务器中间件。

中间件处理程序将在任何其他服务器路由之前在每个请求上运行,以添加或检查标头、记录请求或扩展事件的请求对象。

中间件处理程序不应返回任何内容(也不应关闭或响应请求),只能检查或扩展请求上下文或抛出错误。

示例:
export default defineEventHandler((event) => {
  console.log('New request: ' + getRequestURL(event))
})

export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})

服务器插件

Nuxt会自动读取~/server/plugins目录中的任何文件,并将它们注册为Nitro插件。这允许扩展Nitro的运行时行为并钩入生命周期事件。

export default defineNitroPlugin((nitroApp) => {
  console.log('Nitro plugin', nitroApp)
})

服务器类型

实例
路由参数

服务器路由可以在文件名中使用方括号内的动态参数,如/api/hello/[name].ts,并通过event.context.params访问。

export default defineEventHandler((event) => {
  const name = getRouterParam(event, 'name')

  return `Hello, ${name}!`
})

现在你可以在/api/hello/nuxt上普遍调用此API,并获得Hello, nuxt!

匹配HTTP方法

处理文件名可以使用.get.post.put.delete等后缀来匹配请求的HTTP方法

export default defineEventHandler(() => 'Test get handler')
export default defineEventHandler(() => 'Test post handler')

在上面的示例中,使用以下方式获取/test

  • GET方法:返回Test get handler
  • POST方法:返回Test post handler
  • 任何其他方法:返回405错误

你还可以在目录中使用index.[method].ts来以不同的方式组织代码,这对于创建API命名空间非常有用。

export default defineEventHandler((event) => {
  // 处理`api/foo`端点的GET请求
})
export default defineEventHandler((event) => {
  // 处理`api/foo`端点的POST请求
})
export default defineEventHandler((event) => {
  // 处理`api/foo/bar`端点的GET请求
})
捕获所有路由

捕获所有路由对于后备路由处理非常有用。

例如,创建一个名为~/server/api/foo/[...].ts的文件将为所有不匹配任何路由处理程序的请求注册一个捕获所有路由,如/api/foo/bar/baz

export default defineEventHandler((event) => {
  // event.context.path 获取路由路径:'/api/foo/bar/baz'
  // event.context.params._ 获取路由段:'bar/baz'
  return `Default foo handler`
})

你可以通过使用~/server/api/foo/[...slug].ts来为捕获所有路由设置一个名称,并通过event.context.params.slug访问它。

export default defineEventHandler((event) => {
  // event.context.params.slug 获取路由段:'bar/baz'
  return `Default foo handler`
})
处理请求体
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  return { body }
})

现在你可以使用以下方式普遍调用此API:

<script setup>
async function submit() {
  const { body } = await $fetch('/api/submit', {
    method: 'post',
    body: { test: 123 }
  })
}
</script>

我们在文件名中仅使用submit.post.ts来匹配可以接受请求体的POST方法的请求。在GET请求中使用readBody时,readBody将抛出405 Method Not Allowed HTTP错误。

查询参数

示例查询/api/query?foo=bar&baz=qux

export default defineEventHandler((event) => {
  const query = getQuery(event)

  return { a: query.foo, b: query.baz }
})
错误处理

如果没有抛出错误,将返回状态码200 OK

任何未捕获的错误都将返回500 Internal Server Error HTTP错误。

要返回其他错误代码,请使用createError抛出异常:

export default defineEventHandler((event) => {
  const id = parseInt(event.context.params.id) as number

  if (!Number.isInteger(id)) {
    throw createError({
      statusCode: 400,
      statusMessage: 'ID should be an integer',
    })
  }
  return 'All good'
})
状态码

要返回其他状态码,请使用setResponseStatus实用程序。

例如,返回202 Accepted

export default defineEventHandler((event) => {
  setResponseStatus(event, 202)
})
请求cookie
export default defineEventHandler((event) => {
  const cookies = parseCookies(event)

  return { cookies }
})
高级用法
服务器存储

Nitro提供了一个跨平台的存储层。为了配置额外的存储挂载点,你可以使用nitro.storage服务器插件

添加Redis存储的示例:

使用nitro.storage

export default defineNuxtConfig({
  nitro: {
    storage: {
      redis: {
        driver: 'redis',
        /* redis连接器选项 */
        port: 6379, // Redis端口
        host: "127.0.0.1", // Redis主机
        username: "", // 需要Redis >= 6
        password: "",
        db: 0, // 默认为0
        tls: {} // tls/ssl
      }
    }
  }
})

然后在你的API处理程序中:

export default defineEventHandler(async (event) => {
  // 使用以下方式列出所有键
  const keys = await useStorage('redis').getKeys()

  // 使用以下方式设置键
  await useStorage('redis').setItem('foo', 'bar')

  // 使用以下方式删除键
  await useStorage('redis').removeItem('foo')

  return {}
})

或者,你可以使用服务器插件和运行时配置创建存储挂载点:

import redisDriver from 'unstorage/drivers/redis'

export default defineNitroPlugin(() => {
  const storage = useStorage()

  // 动态传递来自运行时配置或其他来源的凭据
  const driver = redisDriver({
      base: 'redis',
      host: useRuntimeConfig().redis.host,
      port: useRuntimeConfig().redis.port,
      /* 其他redis连接器选项 */
    })

  // 挂载驱动程序
  storage.mount('redis', driver)
})
export default defineNuxtConfig({
  runtimeConfig: {
    redis: { // 默认值
      host: '',
      port: 0,
      /* 其他redis连接器选项 */
    }
  }
})

App.vue

使用pages/时

<template>
  <div>
    <NuxtLayout>
      <NuxtPage/>
    </NuxtLayout>
  </div>
</template>

由于内部使用了 Vue 的组件,它不能被设置为根元素。

请记住,app.vue 是你的 Nuxt 应用的主要组件。你在其中添加的任何内容(JS 和 CSS)都将是全局的,并包含在每个页面中。

app.config.ts

使用App Config文件在应用程序中公开响应式配置。

Nuxt 3提供了一个app.config配置文件,可以在应用程序中公开响应式配置,并在运行时通过生命周期或使用nuxt插件进行更新,同时还可以使用HMR(热模块替换)进行编辑。

你可以使用app.config.ts文件轻松提供运行时应用程序配置。它可以是.ts.js.mjs扩展名之一。

export default defineAppConfig({
  foo: 'bar'
})

请勿将任何机密值放在app.config文件中。它将暴露给用户客户端捆绑包。

用法

要将配置和环境变量公开给应用程序的其他部分,你需要在app.config文件中定义配置。

export default defineAppConfig({
  theme: {
    primaryColor: '#ababab'
  }
})

当将theme添加到app.config中时,Nuxt使用Vite或webpack来打包代码。我们可以使用useAppConfig组合函数在服务器渲染页面和浏览器中普遍访问theme

<script setup>
const appConfig = useAppConfig()

console.log(appConfig.theme)
</script>

nuxt.config.ts

Nuxt可以通过一个单独的nuxt.config文件进行简单配置。

nuxt.config文件的扩展名可以是.js.ts.mjs

export default defineNuxtConfig({
  // 我的Nuxt配置
})

defineNuxtConfig助手可以在全局范围内直接使用,无需导入。

如果你愿意,你可以从nuxt/config中显式导入defineNuxtConfig

import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
  // 我的Nuxt配置
})

Nuxt API参考

组件

ClientOnly

<ClientOnly>组件只在客户端渲染其插槽内容。要仅在客户端导入组件,请在仅客户端的插件中注册组件。

Props

  • placeholderTag | fallbackTag: 指定在服务器端渲染的标签。
  • placeholder | fallback: 指定在服务器端渲染的内容。
<template>
  <div>
    <Sidebar />
    <ClientOnly fallback-tag="span" fallback="加载评论中...">
      <Comment />
    </ClientOnly>
  </div>
</template>

Slots

  • #fallback: 指定在服务器端显示的内容。
<template>
  <div>
    <Sidebar />
    <ClientOnly>
      <!-- ... -->
      <template #fallback>
        <!-- 这将在服务器端渲染 -->
        <p>加载评论中...</p>
      </template>
    </ClientOnly>
  </div>
</template>
NuxtPage

<NuxtPage> 是 Nuxt 内置的组件。它可以用来显示位于 pages/ 目录中的顶级或嵌套页面。

<NuxtPage> 是对 Vue Router 的 组件的封装。
它接受相同的 nameroute 属性。

属性

name:告诉 RouterView 在匹配的路由记录的组件选项中使用对应名称渲染组件。类型:string

route:已解析所有组件的路由位置。类型:RouteLocationNormalized

pageKey:控制何时重新渲染 NuxtPage 组件。类型:stringfunction

Nuxt 会自动扫描并渲染在 /pages 目录中找到的所有 Vue 组件文件,以解析 nameroute

示例

例如,通过传递 static 键,NuxtPage 组件在挂载时只会渲染一次。

<template>
  <NuxtPage page-key="static" />
</template>

你也可以使用基于当前路由的动态键:

<NuxtPage :page-key="route => route.fullPath" />

不要在这里使用 $route 对象,因为它可能会导致 <NuxtPage><Suspense> 一起渲染页面时出现问题。

或者,pageKey 可以作为 key 值通过 <script> 部分中的 /pages 目录中的 Vue 组件的 definePageMeta 来传递。

<script setup lang="ts">
definePageMeta({
  key: route => route.fullPath
})
</script>
页面的引用

要获取页面组件的 ref,请通过 ref.value.pageRef 访问。

<script setup lang="ts">
const page = ref()

function logFoo () {
  page.value.pageRef.foo()
}
</script>

<template>
  <NuxtPage ref="page" />
</template>
NuxtLayout

Nuxt 提供了 组件来在页面和错误页面上显示布局。

你可以使用 <NuxtLayout /> 组件在 app.vueerror.vue 上激活 default 布局。

<template>
  <NuxtLayout>
    页面内容
  </NuxtLayout>
</template>

Props

name: 指定要渲染的布局名称,可以是字符串、响应式引用或计算属性。它必须layouts/ 目录中相应布局文件的名称匹配。

<script setup lang="ts">
// layouts/custom.vue
const layout = 'custom'
</script>

<template>
  <NuxtLayout :name="layout">
    <NuxtPage />
  </NuxtLayout>
</template>
过渡效果

<NuxtLayout /> 通过 <slot /> 渲染传入的内容,然后将其包装在 Vue 的 <Transition /> 组件中以激活布局过渡效果。为了使其按预期工作,建议 <NuxtLayout /> 不是页面组件的根元素。

<template>
  <div>
    <NuxtLayout name="custom">
      <template #header> 页面头部模板内容。 </template>
    </NuxtLayout>
  </div>
</template>
布局的Ref

要获取布局组件的 ref,请通过 ref.value.layoutRef 访问。

<script setup lang="ts">
const layout = ref()

function logFoo () {
  layout.value.layoutRef.foo()
}
</script>

<template>
  <NuxtLayout ref="layout" />
</template>
NuxtLink

<NuxtLink> 是 Vue Router 的 <RouterLink> 组件和 HTML 的 <a> 标签的替代品。它能智能地确定链接是内部链接还是外部链接,并根据可用的优化(预加载、默认属性等)进行渲染。

内部路由

在这个例子中,我们使用 <NuxtLink> 组件将链接到应用程序的另一个页面。

<template>
  <NuxtLink to="/about">
    关于页面
  </NuxtLink>
  <!-- <a href="/about">...</a> (+Vue Router & prefetching) -->
</template>
外部路由

在这个例子中,我们使用 <NuxtLink> 组件链接到一个网站。

<template>
  <NuxtLink to="https://nuxtjs.org">
    Nuxt 网站
  </NuxtLink>
  <!-- <a href="https://nuxtjs.org" rel="noopener noreferrer">...</a> -->
</template>
target和rel属性

在这个例子中,我们使用 <NuxtLink> 组件配合 targetrelnoRel 属性。

<template>
  <NuxtLink to="https://twitter.com/nuxt_js" target="_blank">
    Nuxt Twitter
  </NuxtLink>
  <!-- <a href="https://twitter.com/nuxt_js" target="_blank" rel="noopener noreferrer">...</a> -->

  <NuxtLink to="https://discord.nuxtjs.org" target="_blank" rel="noopener">
    Nuxt Discord
  </NuxtLink>
  <!-- <a href="https://discord.nuxtjs.org" target="_blank" rel="noopener">...</a> -->

  <NuxtLink to="https://github.com/nuxt" no-rel>
    Nuxt GitHub
  </NuxtLink>
  <!-- <a href="https://github.com/nuxt">...</a> -->

  <NuxtLink to="/contact" target="_blank">
    联系页面在新标签页中打开
  </NuxtLink>
  <!-- <a href="/contact" target="_blank" rel="noopener noreferrer">...</a> -->
</template>

*rel="noopener noreferrer" 的作用简要概述如下:

  1. 提高安全性:防止新开窗口(通过 target="_blank" 打开的)操纵原窗口。
  2. 保护隐私:防止目标网站获取用户的来源页面信息。*
  • rel:链接上要应用的 rel 属性值。对于外部链接,默认为 "noopener noreferrer"
  • noRel:如果设置为 true,链接将不会添加 rel 属性
覆盖默认值

你可以通过创建自己的链接组件并使用 defineNuxtLink 来覆盖 <NuxtLink> 的默认值。

export default defineNuxtLink({
  componentName: 'MyNuxtLink',
})

然后,你可以像往常一样使用 <MyNuxtLink /> 组件,并使用你的新默认值。

defineNuxtLink签名
defineNuxtLink({
  componentName?: string;
  externalRelAttribute?: string;
  activeClass?: string;
  exactActiveClass?: string;
  prefetchedClass?: string;
  trailingSlash?: 'append' | 'remove'
}) => Component
  • componentName:定义的 <NuxtLink> 组件的名称。
  • externalRelAttribute:应用于外部链接的默认 rel 属性值。默认为 "noopener noreferrer"。将其设置为 "" 可禁用该属性
  • activeClass:应用于活动链接的默认类。与 Vue Router 的 linkActiveClass 选项 的工作方式相同。默认为 Vue Router 的默认值 ("router-link-active")
  • exactActiveClass:应用于精确活动链接的默认类。与 Vue Router 的 linkExactActiveClass 选项 的工作方式相同。默认为 Vue Router 的默认值 ("router-link-exact-active")
  • prefetchedClass:应用于已预加载链接的默认类
  • trailingSlash:一个选项,用于在 href 中添加或删除尾部斜杠。如果未设置或不匹配有效值 appendremove,它将被忽略。
NuxtLoadingIndicator

在页面导航之间显示一个进度条。

使用方法

在你的 app.vuelayouts/ 中添加 <NuxtLoadingIndicator/>

<template>
  <NuxtLayout>
    <div>
      <NuxtLoadingIndicator /> <!-- 在这里 -->
      <NuxtPage />
    </div>
  </NuxtLayout>
</template>
插槽

你可以通过 loading indicator 的默认插槽传递自定义的 HTML 或组件。

属性

color: 进度条的颜色。可以设置为 false 来关闭显式的颜色样式。

height: 进度条的高度,以像素为单位(默认为 3)。

duration: 进度条的持续时间,以毫秒为单位(默认为 2000)。

throttle: 进度条出现和隐藏的节流时间,以毫秒为单位(默认为 200)。

NuxtErrorBoundary

组件用于处理在其默认插槽中发生的客户端错误。

<NuxtErrorBoundary>在底层使用了Vue的onErrorCaptured钩子。

事件

@error: 当组件的默认插槽抛出错误时触发的事件。

<template>
  <NuxtErrorBoundary @error="logSomeError">
    <!-- ... -->
  </NuxtErrorBoundary>
</template>
插槽

#error: 在出现错误时指定备用内容进行显示。

  <template>
    <NuxtErrorBoundary>
      <!-- ... -->
      <template #error="{ error }">
        <p>发生错误:{{ error }}</p>
      </template>
    </NuxtErrorBoundary>
  </template>
NuxtImg

Nuxt 提供了一个 组件来处理自动图像优化。

<NuxtImg>是原生<img>标签的一个即插即用替代品。

  • 使用内置提供商优化本地和远程图像
  • src转换为提供商优化的URL
  • 根据widthheight自动调整图像大小
  • 在提供sizes选项时生成响应式尺寸
  • 支持原生懒加载以及其他<img>属性
设置

为了使用<NuxtImg>,你需要安装并启用Nuxt Image模块:

npx nuxi@latest module add image
用法

<NuxtImg>直接输出一个原生的img标签(没有任何包装器)。像使用<img>标签一样使用它:

<NuxtImg src="/nuxt-icon.png" />

将会得到:

<img src="/nuxt-icon.png" />
NuxtPicture

Nuxt提供了一个组件来处理自动图片优化。

NuxtPicture是对原生picture标签的一种直接替代方式。

使用NuxtPicture几乎与NuxtImg相同,但它还可以在可能的情况下提供现代格式,如webp

设置

为了使用NuxtPicture,你需要安装并启用Nuxt Image模块:

npx nuxi@latest module add image
Teleport

组件将一个组件传送到DOM中的不同位置。

<Teleport>to目标属性期望一个CSS选择器字符串或实际的DOM节点。Nuxt目前只支持将teleport传送到body,对于其他目标的客户端支持需要使用<ClientOnly>包装器。

传送到body
<template>
  <button @click="open = true">
    打开模态框
  </button>
  <Teleport to="body">
    <div v-if="open" class="modal">
      <p>来自模态框的问候!</p>
      <button @click="open = false">
        关闭
      </button>
    </div>
  </Teleport>
</template>

客户端传送

<template>
  <ClientOnly>
    <Teleport to="#some-selector">
      <!-- 内容 -->
    </Teleport>
  </ClientOnly>
</template>

组合函数

useAppConfig

访问项目中定义的响应式应用配置。

使用方法
const appConfig = useAppConfig()

console.log(appConfig)
useAsyncData

useAsyncData提供了一种在SSR友好的组合式中访问异步解析数据的方式。

在你的页面、组件和插件中,你可以使用useAsyncData来获取异步解析的数据。

useAsyncData是一种组合式,可以直接在设置函数、插件或路由中调用。它返回响应式的组合式,并处理将响应添加到Nuxt负载中,以便在页面水合时从服务器传递到客户端,而不需要在客户端重新获取数据。

用法
<script setup>
const { data, pending, error, refresh } = await useAsyncData(
  'mountains',
  () => $fetch('https://api.nuxtjs.dev/mountains')
)
</script>
监听参数

内置的watch选项允许在检测到任何更改时自动重新运行获取器函数。

<script setup>
const page = ref(1)
const { data: posts } = await useAsyncData(
  'posts',
  () => $fetch('https://fakeApi.com/posts', {
    params: {
      page: page.value
    }
  }), {
    watch: [page]
  }
)
</script>

useAsyncData是编译器转换的保留函数名,因此你不应该将自己的函数命名为useAsyncData

useCookie

useCookie是一个适用于服务器端渲染(SSR)的组合函数,用于读取和写入cookie。

const cookie = useCookie(name, options)

useCookie的ref会自动将cookie值序列化和反序列化为JSON。

示例

下面的示例创建了一个名为counter的cookie。如果该cookie不存在,它将被初始设置为一个随机值。每当我们更新counter变量时,cookie也会相应地更新。

<script setup lang="ts">
const counter = useCookie('counter')

counter.value = counter.value || Math.round(Math.random() * 1000)
</script>

<template>
  <div>
    <h1>计数器: {{ counter || '-' }}</h1>
    <button @click="counter = null">重置</button>
    <button @click="counter--">减少</button>
    <button @click="counter++">增加</button>
  </div>
</template>
选项

Cookie组合函数接受几个选项,可以修改cookie的行为。

大多数选项将直接传递给cookie包。

maxAge / expires

使用这些选项来设置cookie的过期时间。

maxAge:指定一个number(以秒为单位)作为Max-Age Set-Cookie属性的值。 给定的数字将被四舍五入为整数。默认情况下,不设置最大年龄。

expires:指定一个Date对象作为Expires Set-Cookie属性的值。 默认情况下,不设置过期时间。大多数客户端将把它视为“非持久cookie”,并在条件(比如退出Web浏览器应用程序)下删除它。

cookie存储模型规范指出,如果同时设置了expiresmaxAge,则maxAge优先,但并非所有客户端都遵守这一规定, 因此,如果两者都设置了,它们应该指向相同的日期和时间!

如果expiresmaxAge都没有设置,那么cookie将仅在会话期间存在,并在用户关闭浏览器时被删除。

httpOnly

指定一个boolean值作为HttpOnly Set-Cookie属性的值。如果为真,则设置HttpOnly属性;否则不设置。默认情况下,不设置HttpOnly属性。

当将此值设置为true时,请小心,因为符合规范的客户端将不允许客户端JavaScript看到document.cookie中的cookie。

secure

指定一个boolean值作为Secure Set-Cookie属性的值。如果为真,则设置Secure属性;否则不设置。默认情况下,不设置Secure属性。

当将此值设置为true时,请小心,因为符合规范的客户端将不会在将来将cookie发送回服务器,如果浏览器没有HTTPS连接,这可能导致hydration错误。

domain

指定一个值作为Domain Set-Cookie属性的值。默认情况下,不设置域,大多数客户端将仅将cookie应用于当前域。

path

指定一个值作为Path Set-Cookie属性的值。默认情况下,路径被认为是"默认路径"

sameSite

指定一个booleanstring值作为SameSite Set-Cookie属性的值。

  • trueSameSite属性设置为Strict以进行严格的同站点执行。
  • false不设置SameSite属性。
  • 'lax'SameSite属性设置为Lax以进行宽松的同站点执行。
  • 'none'SameSite属性设置为None以进行明确的跨站点cookie。
  • 'strict'SameSite属性设置为Strict以进行严格的同站点执行。

encode

指定一个函数,用于编码cookie的值。由于cookie的值具有有限的字符集(必须是简单字符串),因此此函数可用于将一个值编码为适合cookie值的字符串。

默认编码器是JSON.stringify + encodeURIComponent

decode

指定一个函数,用于解码cookie的值。由于cookie的值具有有限的字符集(必须是简单字符串),因此此函数可用于将先前编码的cookie值解码为JavaScript字符串或其他对象。

默认解码器是decodeURIComponent + destr

如果从此函数抛出错误,则将返回原始的、未解码的cookie值作为cookie的值。

default

指定一个返回cookie的默认值的函数。该函数还可以返回一个Ref

watch

指定一个booleanstring值,用于监听cookie的ref数据。

  • true - 将监听cookie的ref数据变化以及其嵌套属性(默认)。

  • shallow - 只监听cookie的ref数据的顶级属性变化。

  • false - 不监听cookie的ref数据变化。

    示例1

    <script setup lang="ts">
    const user = useCookie(
      'userInfo',
      {
        default: () => ({ score: -1 }),
        watch: false
      }
    )
    
    if (user.value && user.value !== null) {
      user.value.score++; // userInfo cookie不会随此更改而更新
    }
    </script>
    
    <template>
      <div>用户分数: {{ user?.score }}</div>
    </template>
    

    示例2

    <script setup lang="ts">
    const list = useCookie(
      'list',
      {
        default: () => [],
        watch: 'shallow'
      }
    )
    
    function add() {
      list.value?.push(Math.round(Math.random() * 1000))
      // list cookie不会随此更改而更新
    }
    
    function save() {
      if (list.value && list.value !== null) {
        list.value = [...list.value]
        // list cookie随此更改而更新
      }
    }
    </script>
    
    <template>
      <div>
        <h1>列表</h1>
        <pre>{{ list }}</pre>
        <button @click="add">添加</button>
        <button @click="save">保存</button>
      </div>
    </template>
    
API路由中的Cookies

你可以使用h3包中的getCookiesetCookie来在服务端API路由中设置cookie。

export default defineEventHandler(event => {
  // 读取counter cookie
  let counter = getCookie(event, 'counter') || 0

   // 将counter cookie增加1
  setCookie(event, 'counter', ++counter)

  // 发送JSON响应
  return { counter }
})
useError

该可组合函数返回正在处理的全局Nuxt错误,并且在客户端和服务器上都可用。

const error = useError()

useError在状态中设置一个错误,并在组件之间创建一个响应式且支持SSR的全局Nuxt错误。

Nuxt错误具有以下属性:

interface {
  // HTTP响应状态码
  statusCode: number
  // HTTP响应状态消息
  statusMessage: string
  // 错误消息
  message: string
}
useFetch

使用一个与SSR兼容的可组合函数从API端点获取数据。

这个可组合函数提供了一个方便的封装,包装了useAsyncData$fetch。它根据URL和fetch选项自动生成一个键,根据服务器路由提供请求URL的类型提示,并推断API响应类型。

useFetch是一个可组合函数,可以直接在设置函数、插件或路由中调用。它返回响应式的可组合函数,并处理将响应添加到Nuxt的负载中,以便在页面水合时可以从服务器传递给客户端,而无需在客户端重新获取数据。

用法
<script setup>
const route = useRoute()

const { data, pending, error, refresh } = await useFetch(`https://api.nuxtjs.dev/mountains/${route.params.slug}`, {
  pick: ['title']
})
</script>

使用query选项,您可以向查询中添加搜索参数。此选项是从unjs/ofetch扩展的,并使用unjs/ufo创建URL。对象会自动转换为字符串。

const param1 = ref('value1')
const { data, pending, error, refresh } = await useFetch('https://api.nuxtjs.dev/mountains', {
  query: { param1, param2: 'value2' }
})

结果为https://api.nuxtjs.dev/mountains?param1=value1&param2=value2

拦截器

const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
  onRequest({ request, options }) {
    // 设置请求头
    options.headers = options.headers || {}
    options.headers.authorization = '...'
  },
  onRequestError({ request, options, error }) {
    // 处理请求错误
  },
  onResponse({ request, response, options }) {
    // 处理响应数据
    localStorage.setItem('token', response._data.token)
  },
  onResponseError({ request, response, options }) {
    // 处理响应错误
  }
})

useFetch是编译器转换的保留函数名,因此您不应将自定义函数命名为useFetch

useLazyAsyncData

这个封装了 useAsyncData 的包装器立即触发导航。

默认情况下,useAsyncData会阻塞导航,直到其异步处理程序解析完成。useLazyAsyncDatauseAsyncData周围提供了一个包装器,通过将lazy选项设置为true,在处理程序解析之前触发导航。

<script setup lang="ts">
/* 在获取完成之前,导航将会发生。
  在组件的模板中直接处理挂起和错误状态
*/
const { pending, data: count } = await useLazyAsyncData('count', () => $fetch('/api/count'))

watch(count, (newCount) => {
  // 因为 count 可能最初为 null,你不会立即访问到它的内容,但你可以监视它。
})
</script>

<template>
  <div>
    {{ pending ? '加载中' : count }}
  </div>
</template>
useLazyFetch

这个对useFetch的封装会立即触发导航。

默认情况下,useFetch在其异步处理程序解析之前会阻止导航。useLazyFetch提供了一个包装器,将useFetch包装起来,通过将lazy选项设置为true来在处理程序解析之前触发导航。

<script setup lang="ts">
/* 在获取完成之前导航将会发生。
  在组件模板中直接处理待处理和错误状态。
*/
const { pending, data: posts } = await useLazyFetch('/api/posts')
watch(posts, (newPosts) => {
  // 因为posts可能最初为null,你不能立即访问它的内容,但可以监视它。
})
</script>

<template>
  <div v-if="pending">
    加载中 ...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- 做一些操作 -->
    </div>
  </div>
</template>
useRouter
<script setup>
const router = useRouter()
</script>

如果你只需要在模板中使用路由实例,可以使用 $router

<template>
  <button @click="$router.back()">返回</button>
</template>

如果你有一个 pages/ 目录,那么 useRouter 的行为与 vue-router 提供的行为相同。

基本操作
  • addRoute(): 向路由实例添加新的路由。可以提供 parentName 将新路由添加为现有路由的子路由。
  • removeRoute(): 根据名称移除现有路由。
  • getRoutes(): 获取所有路由记录的完整列表。
  • hasRoute(): 检查是否存在具有给定名称的路由。
  • resolve(): 返回路由位置的规范化版本。还包括一个 href 属性,其中包含任何现有的基础路径。
const router = useRouter()

router.addRoute({ name: 'home', path: '/home', component: Home })
router.removeRoute('home')
router.getRoutes()
router.hasRoute('home')
router.resolve({ name: 'home' })
基于History API
  • back(): 如果可能,返回上一页,与 router.go(-1) 相同。
  • forward(): 如果可能,前进到下一页,与 router.go(1) 相同。
  • go(): 在历史记录中向前或向后移动,不受 router.back()router.forward() 中施加的层次结构限制。
  • push(): 通过将新条目推入历史堆栈来以编程方式导航到新的 URL。建议使用 navigateTo 代替。
  • replace(): 通过替换当前路由历史堆栈中的当前条目来以编程方式导航到新的 URL。建议使用 navigateTo 代替。
const router = useRouter()

router.back()
router.forward()
router.go(3)
router.push({ path: "/home" })
router.replace({ hash: "#bio" })

工具函数

$fetch

Nuxt使用ofetch来全局暴露$fetch辅助函数,用于在Vue应用程序或API路由中进行HTTP请求。

在组件中使用$fetch而不使用useAsyncData进行包装会导致数据被获取两次:首先在服务器端获取,然后在客户端进行混合渲染期间再次获取,因为$fetch不会将状态从服务器传递到客户端。因此,获取将在两端执行,因为客户端需要再次获取数据。

我们建议在获取组件数据时使用useFetchuseAsyncData + $fetch来防止重复获取数据。

<script setup lang="ts">
// 在SSR中数据将被获取两次,一次在服务器端,一次在客户端。
const dataTwice = await $fetch('/api/item')

// 在SSR中,数据仅在服务器端获取并传递到客户端。
const { data } = await useAsyncData('item', () => $fetch('/api/item'))

// 你也可以使用useFetch作为useAsyncData + $fetch的快捷方式
const { data } = await useFetch('/api/item')
</script>

你可以在只在客户端执行的任何方法中使用$fetch

<script setup lang="ts">
function contactForm() {
  $fetch('/api/contact', {
    method: 'POST',
    body: { hello: 'world '}
  })
}
</script>

<template>
  <button @click="contactForm">联系我们</button>
</template>

$fetch是在Nuxt中进行HTTP调用的首选方式

abortNavigation
示例

下面的示例演示了如何在路由中间件中使用 abortNavigation 来防止未经授权的路由访问:

export default defineNuxtRouteMiddleware((to, from) => {
  const user = useState('user')

  if (!user.value.isAuthorized) {
    return abortNavigation()
  }
 
  if (to.path !== '/edit-post') {
    return navigateTo('/edit-post')
  }
})

err 作为字符串

你可以将错误作为字符串传递:

export default defineNuxtRouteMiddleware((to, from) => {
  const user = useState('user')

  if (!user.value.isAuthorized) {
    return abortNavigation('权限不足。')
  }
})

err 作为错误对象

你可以将错误作为 Error 对象传递,例如通过 catch 块捕获到的错误:

export default defineNuxtRouteMiddleware((to, from) => {
  try {
    /* 可能会抛出错误的代码 */
  } catch (err) {
    return abortNavigation(err)
  }
})

本文节选至Nuxt3官网,也是我的学习笔记,谢谢大家观看~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酒尘&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值