components

components/ 目录是放置所有 Vue 组件的地方。

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

目录结构
-| components/
---| AppHeader.vue
---| AppFooter.vue
app.vue
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

组件名称

如果您在嵌套目录中有一个组件,例如:

目录结构
-| components/
---| base/
-----| foo/
-------| Button.vue

... 那么组件的名称将基于其自身的路径目录和文件名,重复的段会被移除。因此,组件的名称将是:

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

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

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      pathPrefix: false, // [!代码 ++]
    },
  ],
});

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

动态组件

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

例如:

pages/index.vue
<script setup lang="ts">
import { SomeComponent } from '#components'

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

<template>
  <component :is="clickable ? MyButton : 'div'" />
  <component :is="SomeComponent" />
</template>
如果您使用 resolveComponent 来处理动态组件,请确保只插入组件的名称,该名称必须是一个字面字符串,而不能是变量或包含变量。该字符串在编译阶段会被静态分析。
观看 Daniel Roe 的短视频,了解 resolveComponent

另外,虽然不推荐,您也可以全局注册所有组件,这将为所有组件创建异步代码块,并使它们在整个应用程序中可用。

  export default defineNuxtConfig({
    components: {
+     global: true,
+     dirs: ['~/components']
    },
  })

您还可以通过将某些组件放置在 ~/components/global 目录中,或使用 .global.vue 后缀在文件名中选择性地全局注册一些组件。如上所述,每个全局组件都在一个单独的代码块中渲染,因此请小心不要过度使用此功能。

global 选项也可以针对每个组件目录单独设置。

动态导入

要动态导入组件(也称为懒加载组件),您只需在组件名称前添加 Lazy 前缀。这在组件不总是需要时特别有用。

通过使用 Lazy 前缀,您可以延迟加载组件代码,直到合适的时机,这对优化 JavaScript 包大小非常有帮助。

pages/index.vue
<script setup lang="ts">
const show = ref(false)
</script>

<template>
  <div>
    <h1>山脉</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">显示列表</button>
  </div>
</template>

延迟(或懒加载)水合

懒加载组件非常适合控制应用中的代码块大小,但它们并不总是提升运行时性能,因为它们仍然会急切加载,除非有条件地渲染。在现实世界的应用中,一些页面可能包含大量内容和组件,而大多数情况下,并不需要所有这些组件在页面加载时都是交互式的。让它们全部急切加载可能会对性能产生负面影响。

为了优化您的应用,您可能希望延迟一些组件的水合,直到它们可见,或直到浏览器完成更重要的任务。

Nuxt 支持这使用延迟(或懒加载)水合,允许您控制何时使组件变为交互式。

水合策略

Nuxt 提供了一系列内置的水合策略。每个懒加载组件只能使用一个策略。

目前,Nuxt 的内置懒加载水合仅在单文件组件(SFCs)中工作,并且要求您在模板中定义 prop(而不是通过 v-bind 展开 props 对象)。它也不适用于来自 #components 的直接导入。

hydrate-on-visible

当组件在视口中可见时进行水合。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
阅读有关 hydrate-on-visible 的更多选项。
在底层,它使用 Vue 内置的 hydrateOnVisible 策略

hydrate-on-idle

当浏览器处于空闲状态时进行水合。如果您需要尽快加载该组件,但不想阻塞关键渲染路径,这是合适的。

您还可以传递一个数字作为最大超时。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-idle />
  </div>
</template>
在底层,它使用 Vue 内置的 hydrateOnIdle 策略

hydrate-on-interaction

在特定交互后(例如,点击,鼠标悬停)进行水合。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-interaction="mouseover" />
  </div>
</template>

如果您未传递事件或事件列表,默认情况下在 pointerenterfocus 上进行水合。

在底层,它使用 Vue 内置的 hydrateOnInteraction 策略

hydrate-on-media-query

当窗口匹配媒体查询时进行水合。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
  </div>
</template>
在底层,它使用 Vue 内置的 hydrateOnMediaQuery 策略

hydrate-after

在指定延迟(以毫秒为单位)后进行水合。

pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-after="2000" />
  </div>
</template>

hydrate-when

根据布尔条件进行水合。

pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-when="isReady" />
  </div>
</template>
<script setup lang="ts">
const isReady = ref(false)
function myFunction() {
  // 触发自定义水合策略...
  isReady.value = true
}
</script>

hydrate-never

永远不进行水合组件。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-never />
  </div>
</template>

监听水合事件

所有延迟水合组件在进行水合时都会发出 @hydrated 事件。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible @hydrated="onHydrate" />
  </div>
</template>

<script setup lang="ts">
function onHydrate() {
  console.log("组件已经被水合!")
}
</script>

注意事项和最佳实践

延迟水合可以提供性能收益,但正确使用它至关重要:

  1. 优先考虑视口内容: 避免对关键的、位于折叠的内容使用延迟水合。它最适合于不立即需要的内容。
  2. 条件渲染: 当在懒加载组件上使用 v-if="false" 时,您可能不需要延迟水合。您可以直接使用普通懒加载组件。
  3. 共享状态: 请注意跨多个组件的共享状态(v-model)。在一个组件中更新模型可能会触发与该模型绑定的所有组件的水合。
  4. 使用每种策略的预期用例: 每种策略都是针对特定目的进行优化的。
    • hydrate-when 最适合于可能不需要始终进行水合的组件。
    • hydrate-after 适合可以等待特定时间的组件。
    • hydrate-on-idle 适合可以在浏览器处于空闲时进行水合的组件。
  5. 避免在交互组件上使用 hydrate-never 如果组件需要用户交互,则不应设置为永远不水合。

直接导入

如果您希望显式导入组件,或者需要绕过 Nuxt 的自动导入功能,可以从 #components 显式导入组件。

pages/index.vue
<script setup lang="ts">
import { NuxtLink, LazyMountainsList } from '#components'

const show = ref(false)
</script>

<template>
  <div>
    <h1>山脉</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">显示列表</button>
    <NuxtLink to="/">首页</NuxtLink>
  </div>
</template>

自定义目录

默认情况下,仅扫描 ~/components 目录。如果您希望添加其他目录,或更改在该目录子文件夹内扫描组件的方式,则可以将其他目录添加到配置中:

nuxt.config.ts
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' },

    // 如果您希望对此进行重写,这一点很重要
    // sub-directories of `~/components` 的设置顺序至关重要。
    //
    // ~/components/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components'
  ]
})
任何嵌套目录需要优先添加,因为它们按顺序扫描。

npm 包

如果您希望自动导入来自 npm 包的组件,可以在一个 本地模块 中使用 addComponent 来注册它们。

import { addComponent, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    // import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
    addComponent({
      name: 'MyAutoImportedComponent',
      export: 'MyComponent',
      filePath: 'my-npm-package',
    })
  },
})

组件扩展

默认情况下,任何在 nuxt.config.tsextensions 键中 指定的扩展名都被视为组件。 如果您需要限制应注册为组件的文件扩展名,您可以使用组件目录声明的扩展形式以及其 extensions 键:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      extensions: ['.vue'], // [!代码 ++]
    }
  ]
})

客户端组件

如果一个组件仅用于在客户端渲染,您可以在您的组件名后添加 .client 后缀。

目录结构
| components/
--| Comments.client.vue
pages/example.vue
<template>
  <div>
    <!-- 这个组件只会在客户端渲染 -->
    <Comments />
  </div>
</template>
此功能仅适用于 Nuxt 的自动导入和 #components 导入。从其真实路径显式导入这些组件不会将其转换为仅客户端组件。
.client 组件仅在挂载后渲染。要在 onMounted() 时访问渲染的模板,请在 onMounted() 钩子的回调中添加 await nextTick()
您也可以通过 <ClientOnly> 组件实现类似的效果。

服务器组件

服务器组件允许在客户端应用中进行单个组件的服务器渲染。即使您正在生成静态网站,也可以在 Nuxt 中使用服务器组件。这使得构建混合动态组件、服务器渲染的 HTML 甚至静态标记代码块的复杂网站成为可能。

服务器组件可以单独使用,也可以与 客户端组件 配对使用。

观看 Learn Vue 视频,了解 Nuxt 服务器组件。
阅读 Daniel Roe 的 Nuxt 服务器组件指南。

独立服务器组件

独立服务器组件将始终在服务器上渲染,也称为 Islands 组件。

当它们的 props 更新时,这将导致网络请求,从而在该位置更新渲染的 HTML。

服务器组件当前是实验性的,为了使用它们,您需要在 nuxt.config 中启用 'component islands' 功能:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})

现在,您可以注册仅服务器组件,使用 .server 后缀并在应用程序中自动使用它们。

目录结构
-| components/
---| HighlightedMarkdown.server.vue
pages/example.vue
<template>
  <div>
    <!--
      这将自动在服务器上渲染,这意味着您的 markdown 解析 + 高亮库不会包含在客户端包中。
     -->
    <HighlightedMarkdown markdown="# 标题" />
  </div>
</template>

仅服务器组件在底层使用 <NuxtIsland>,这意味着 lazy 属性和 #fallback 插槽都向下传递。

服务器组件(和 islands)必须具有单个根元素。(HTML 注释也被视为元素。)
道具通过 URL 查询参数传递给服务器组件,因此受到 URL 长度的限制,因此要小心不要通过道具向服务器组件传递大量数据。
在其他 islands 内嵌套 islands 时要小心,因为每个 island 都会增加一些额外的开销。
服务器唯一组件和岛屿组件的大多数功能,例如插槽和客户端组件,仅适用于单文件组件。

服务器组件中的客户端组件

此功能需要在您的配置中将 experimental.componentIslands.selectiveClient 设置为 true。

您可以通过为希望在客户端加载的组件设置 nuxt-client 属性来部分水合组件。

components/ServerWithClient.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# 标题" />
    <!-- 计数器将在客户端加载和水合 -->
    <Counter nuxt-client :count="5" />
  </div>
</template>
这仅在服务器组件中有效。客户端组件的插槽仅在 experimental.componentIsland.selectiveClient 设置为 'deep' 时有效,并且由于它们在服务器端渲染,因此它们在客户端不可交互。

服务器组件上下文

在渲染服务器唯一组件或岛屿组件时,<NuxtIsland> 会发起一个 fetch 请求,该请求返回一个 NuxtIslandResponse。(如果在服务器上渲染,这是内部请求,如果在客户端导航时渲染,则可以在网络选项卡中看到该请求。)

这意味着:

  • 将在服务器端创建一个新的 Vue 应用程序,以创建 NuxtIslandResponse
  • 渲染组件时将创建一个新的 "island context"。
  • 您无法从应用程序的其余部分访问 "island context",也无法从 island 组件访问应用程序其余部分的上下文。换句话说,服务器组件或岛屿是与应用程序的其余部分 隔离 的。
  • 当渲染 island 时,您的插件将再次运行,除非它们设置了 env: { islands: false }(您可以在对象语法插件中做到这一点)。

在 island 组件中,您可以通过 nuxtApp.ssrContext.islandContext 访问其 island 上下文。请注意,尽管 island 组件仍被标记为实验性,但此上下文的格式可能会发生变化。

插槽可以是交互式的,并被包装在 display: contents;<div> 中。

与客户端组件配对

在这种情况下,.server + .client 组件是组件的两个“部分”,可以用于服务器和客户端在组件的独立实现的高级用例。

目录结构
-| components/
---| Comments.client.vue
---| Comments.server.vue
pages/example.vue
<template>
  <div>
    <!-- 该组件将在服务器上渲染 Comments.server,然后在浏览器中挂载后渲染 Comments.client -->
    <Comments />
  </div>
</template>

内置 Nuxt 组件

Nuxt 提供了一些组件,包括 <ClientOnly><DevOnly>。您可以在 API 文档中阅读更多有关它们的信息。

Read more in Docs > API.

库作者

创建带有自动树摇动和组件注册的 Vue 组件库非常简单。✨

您可以使用 components:dirs 钩子来扩展目录列表,而无需在您的 Nuxt 模块中要求用户配置。

想象一下这样的目录结构:

目录结构
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.js
-| pages/
---| index.vue
-| nuxt.config.js

然后在 awesome-ui/nuxt.js 中,您可以使用 components:dirs 钩子:

import { defineNuxtModule, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  hooks: {
    'components:dirs': (dirs) => {
      const { resolve } = createResolver(import.meta.url)
      // 将 ./components 目录添加到列表中
      dirs.push({
        path: resolve('./components'),
        prefix: 'awesome'
      })
    }
  }
})

就是这样!现在在您的项目中,您可以在 nuxt.config 文件中将您的 UI 库作为 Nuxt 模块导入:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['awesome-ui/nuxt']
})

... 并在我们的 pages/index.vue 中直接使用模块组件(以 awesome- 为前缀):

<template>
  <div>
    我的 <AwesomeButton>UI 按钮</AwesomeButton>    <awesome-alert>这是一个警报!</awesome-alert>
  </div>
</template>

它将仅在使用时自动导入组件,并在更新 node_modules/awesome-ui/components/ 中的组件时支持热更新。

Read and edit a live example in Docs > Examples > Features > Auto Imports.