---
title: "components"
description: "components/ 目录是放置所有 Vue 组件的地方。"
canonical_url: "https://nuxt.zhcndoc.com/docs/3.x/directory-structure/components"
---
# components

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

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

```bash [目录结构]
-| components/
---| AppHeader.vue
---| AppFooter.vue
```

```html [app.vue]
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>
```

## 组件名称

如果您在嵌套目录中有一个组件，比如：

```bash [目录结构]
-| components/
---| base/
-----| foo/
-------| Button.vue
```

……那么组件的名称将基于其路径目录和文件名，重复的部分将被去除。因此，该组件的名称将是：

```html
<BaseFooButton />
```

::note
为了更清晰起见，建议组件的文件名与其名称匹配。所以，在上述示例中，你可以将 `Button.vue` 重命名为 `BaseFooButton.vue`。
::

如果您只想根据名称而非路径来自动导入组件，则需要使用配置对象的扩展形式，将 `pathPrefix` 选项设置为 `false`：

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

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

## 动态组件

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

例如：

```vue [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>
```

::important
如果您使用 `resolveComponent` 来处理动态组件，请确保只插入组件的名称，该名称必须是字面量字符串，不能是变量或包含变量。该字符串会在编译阶段进行静态分析。
::

::video-accordion{title="观看 Daniel Roe 关于 resolveComponent() 的简短视频" videoId="4kq8E5IUM2U"}
::

另外，虽然不推荐，您可以全局注册所有组件，这会为所有组件生成异步代码块，并使其在整个应用中可用。

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

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

::note
`global` 选项也可针对每个组件目录设置。
::

## 动态导入

若要动态导入组件（也称为懒加载组件），只需在组件名称前加上 `Lazy` 前缀即可。如果组件并非总是需要，这尤其有用。

使用 `Lazy` 前缀可以延迟加载组件代码至合适的时机，有助于优化 JavaScript 包的大小。

```vue [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 提供了一系列内建的水合策略。每个延迟组件只能使用一种策略。

::note
对惰性水化组件的任何属性更改都会立即触发水化。（例如，在带有 `hydrate-never` 的组件上更改属性会导致其水化）
::

::warning
目前，Nuxt 内建的延迟水合只适用于单文件组件（SFC），且需要您在模板中定义 prop（而非通过 `v-bind` 展开 prop 对象）。它也不支持从 `#components` 的直接导入。
::

#### `hydrate-on-visible`

当组件在视口内变得可见时水合。

```vue [pages/index.vue]
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
```

::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver" title="IntersectionObserver 选项"}
进一步阅读有关 `hydrate-on-visible` 的选项。
::

::note
底层使用了 Vue 内建的 [`hydrateOnVisible` 策略](https://vuejs.org/guide/components/async#hydrate-on-visible)。
::

#### `hydrate-on-idle`

当浏览器空闲时水合组件。适合需要尽快加载组件，但不阻塞关键渲染路径的场景。

也可以传入数字，作为最大超时时间。

```vue [pages/index.vue]
<template>
  <div>
    <LazyMyComponent hydrate-on-idle />
  </div>
</template>
```

::note
底层使用了 Vue 内建的 [`hydrateOnIdle` 策略](https://vuejs.org/guide/components/async#hydrate-on-idle)。
::

#### `hydrate-on-interaction`

在指定交互事件后水合组件（例如 click、mouseover）。

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

如果不传递事件或事件列表，默认会在 `pointerenter`、`click` 和 `focus` 时水合。

::note
底层使用了 Vue 内建的 [`hydrateOnInteraction` 策略](https://vuejs.org/guide/components/async#hydrate-on-interaction)。
::

#### `hydrate-on-media-query`

当窗口匹配指定媒体查询时水合组件。

```vue [pages/index.vue]
<template>
  <div>
    <LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
  </div>
</template>
```

::note
底层使用了 Vue 内建的 [`hydrateOnMediaQuery` 策略](https://vuejs.org/guide/components/async#hydrate-on-media-query)。
::

#### `hydrate-after`

在指定延迟（毫秒）后水合组件。

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

#### `hydrate-when`

基于布尔条件水合组件。

```vue [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`

永不水合组件。

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

### 监听水合事件

所有延迟水合组件在水合时都会触发一个 `@hydrated` 事件。

```vue [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` 导入组件。

```vue [pages/index.vue]
<script setup lang="ts">
import { LazyMountainsList, NuxtLink } 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` 目录会被扫描。如果你想添加其他目录，或修改子文件夹中的组件扫描方式，可以向配置添加额外目录：

```ts twoslash [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' },

    // 如果有覆盖 `~/components` 子目录的配置，请确保这一条在最后。
    //
    // ~/components/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components',
  ],
})
```

::note
需要首先添加所有嵌套目录，因为他们按顺序扫描。
::

## npm 包

如果您想从 npm 包自动导入组件，可以在[本地模块](https://nuxt.zhcndoc.com/docs/3.x/directory-structure/modules)中使用 [`addComponent`](https://nuxt.zhcndoc.com/docs/3.x/api/kit/components#addcomponent) 注册它们。

::code-group
```ts twoslash [~/modules/register-component.ts]
import { addComponent, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup () {
    // 从 'my-npm-package' 导入 { MyComponent as MyAutoImportedComponent }
    addComponent({
      name: 'MyAutoImportedComponent',
      export: 'MyComponent',
      filePath: 'my-npm-package',
    })
  },
})
```

```vue [app.vue]
<template>
  <div>
    <!-- 组件使用我们指定的名字，并被自动导入 -->
    <MyAutoImportedComponent />
  </div>
</template>
```
::

## 组件扩展名

默认情况下，[`nuxt.config.ts` 的 `extensions` 键](https://nuxt.zhcndoc.com/docs/3.x/api/nuxt-config#extensions)中指定扩展名的任何文件都会被视为组件。
如果需要限制注册为组件的文件扩展名，可以使用组件目录声明的扩展形式及其 `extensions` 键：

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

## 客户端组件

如果组件仅打算在客户端渲染，可以给组件加上 `.client` 后缀。

```bash [目录结构]
| components/
--| Comments.client.vue
```

```vue [pages/example.vue]
<template>
  <div>
    <!-- 该组件只会在客户端渲染 -->
    <Comments />
  </div>
</template>
```

::note
此功能仅适用于 Nuxt 的自动导入和 `#components` 导入。若显式从真实路径导入组件，无法将其转换为仅客户端组件。
::

::important
`.client` 组件仅在挂载后渲染。若想使用 `onMounted()` 访问渲染后模板，请在 `onMounted()` 回调中添加 `await nextTick()`。
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/components/client-only"}
您也可以使用 `<ClientOnly>` 组件实现类似效果。
::

## 服务端组件

服务端组件允许在客户端应用中针对单个组件进行服务器渲染。即使是生成静态站点，也可以在 Nuxt 中使用服务端组件。这允许构建混合动态组件、服务器渲染 HTML 甚至静态标记的复杂站点。

服务端组件可以单独使用，也可以与[客户端组件](https://nuxt.zhcndoc.com/docs/3.x/directory-structure/components#paired-with-a-client-component)配合使用。

::video-accordion{title="观看 Learn Vue 关于 Nuxt 服务器组件的视频" videoId="u1yyXe86xJM"}
::

::tip{icon="i-lucide-newspaper" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
阅读 Daniel Roe 的 Nuxt 服务端组件指南。
::

### 独立的服务端组件

独立的服务端组件始终在服务器端渲染，也称为“Islands”组件。

当它们的 props 更新时，会发起网络请求，原地更新渲染的 HTML。

服务端组件目前处于试验阶段，需要在 `nuxt.config` 中启用“组件岛屿（component islands）”功能：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    componentIslands: true,
  },
})
```

现在，你可以用 `.server` 后缀注册仅服务端组件，并在应用中自动使用它们。

```bash [目录结构]
-| components/
---| HighlightedMarkdown.server.vue
```

```vue [pages/example.vue]
<template>
  <div>
    <!--
      该组件将自动在服务器端渲染，意味着 markdown 解析和高亮等库不会被打包到客户端。
     -->
    <HighlightedMarkdown markdown="# 标题" />
  </div>
</template>
```

仅服务端组件底层使用 [`<NuxtIsland>`](https://nuxt.zhcndoc.com/docs/3.x/api/components/nuxt-island)，这意味着 `lazy` prop 和 `#fallback` 插槽都会传递给它。

::warning
服务端组件（及 islands）必须只有单个根元素。（HTML 注释也视为元素。）
::

::warning
Props 通过 URL 查询参数传递给服务端组件，因此受 URL 长度限制，避免传递大量数据。
::

::note
服务端组件的 props 来自请求（URL 查询或 body），因此应将其视为不可信输入。Nuxt 会拒绝最可能无意间泄漏的 props：岛屿未声明的顶层 `as`（未声明的 prop 会作为属性透传到岛屿的根节点），以及在启用 `vue.runtimeCompiler` 时，props 中任意位置的 `template`。除此之外，避免将未验证的 props 传入动态组件解析（`<component :is>`{lang="vue"}、`h()`、`resolveDynamicComponent()`，或多态的 `as` / `asChild` prop），因为字符串可以解析为任何已注册组件或 HTML 元素。

组件未声明的 props 会作为属性透传到其单个根元素，因此根元素是多态组件（例如来自 `reka-ui` / `@nuxt/ui`）的岛屿可能会接收到你未绑定的属性。请在这类岛屿上设置 `defineOptions({ inheritAttrs: false })`{lang="ts"}，或声明你接受的 props。

若要根据调用方输入切换组件，请通过允许列表将判别值映射到已导入的组件，而不要直接传递原始 prop：

```vue
<script setup lang="ts">
import type { Component } from 'vue'
import CardA from './CardA.vue'
import CardB from './CardB.vue'

const props = defineProps<{ variant: string }>()
const allowed: Record<string, Component> = { a: CardA, b: CardB }
const component = allowed[props.variant] ?? CardA
</script>

<template>
  <component :is="component" />
</template>
```
::

::warning
嵌套岛屿组件时需注意，每个岛屿都会带来额外开销。
::

::warning
大多数面向服务端和岛屿组件的功能，如插槽和客户端组件，仅对单文件组件可用。
::

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

::note
此功能需要在配置中启用 `experimental.componentIslands.selectiveClient`。
::

你可以通过给希望在客户端加载的组件添加 `nuxt-client` 属性来进行部分水合。

```vue [components/ServerWithClient.vue]
<template>
  <div>
    <HighlightedMarkdown markdown="# 标题" />
    <!-- Counter 将在客户端加载并水合 -->
    <Counter
      nuxt-client
      :count="5"
    />
  </div>
</template>
```

::note
此功能仅在服务端组件内有效。客户端组件的插槽仅在 `experimental.componentIsland.selectiveClient` 设置为 `'deep'` 时可用，且因为服务端渲染，客户端交互不可用。
::

#### 服务端组件上下文

渲染服务端或岛屿组件时，`<NuxtIsland>` 会发起一个请求，返回 `NuxtIslandResponse`。（如果服务端渲染，则为内部请求；客户端导航渲染，则可在网络面板中看到该请求。）

这意味着：

- 服务器端将新建一个 Vue 应用以生成 `NuxtIslandResponse`。
- 渲染组件时会创建新的“岛屿上下文”。
- 你不能从应用其他部分访问该上下文，也不能从岛屿组件访问其他部分的上下文。换言之，服务端组件或岛屿与应用隔离。
- 在渲染岛屿时，插件会重新运行，除非在对象语法插件中设置 `env: { islands: false }`。

::important
路由中间件不会在渲染岛屿组件时执行。中间件是路由概念，适用于页面，不适用于控制组件渲染。
::

::important
`useRoute()` 和其他 `vue-router` 组合式函数不会在服务端（岛屿）组件内跟踪当前页面路由。该岛屿是在其自身隔离的 Vue 应用中渲染的，并且只以它的 props（以及任何显式上下文）作为键，这正是让岛屿可以独立于所渲染页面进行缓存的原因。在岛屿内，`useRoute()` 反映的是岛屿自己的请求，而不是用户当前所在的页面。

如果某个岛屿需要当前路由的信息，请显式传入——可以作为父组件的 props 传递，或通过 `<NuxtIsland>` 上的 `context` 属性传递（在岛屿内部可从 `nuxtApp.ssrContext.islandContext` 读取）。
::

在岛屿组件内部，你可以通过 `nuxtApp.ssrContext.islandContext` 访问其岛屿上下文。请注意，虽然岛屿组件仍处于实验阶段，但该上下文的格式未来可能会变化。

::note
插槽可交互，且会被包裹在带有 `display: contents;` 的 `<div>` 中。
::

### 搭配客户端组件

此情况下，`.server` + `.client` 组件为组件的两个“半边”，用于高级场景，在服务端和客户端分别实现不同逻辑。

```bash [目录结构]
-| components/
---| Comments.client.vue
---| Comments.server.vue
```

```vue [pages/example.vue]
<template>
  <div>
    <!-- 该组件先渲染服务端的 Comments.server，浏览器挂载后切换为 Comments.client -->
    <Comments />
  </div>
</template>
```

## 内置 Nuxt 组件

Nuxt 提供了多个内置组件，包括 `<ClientOnly>` 和 `<DevOnly>`，详情请参阅 API 文档。

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api"}
::

## 库作者

制作带自动树摇和组件注册的 Vue 组件库非常简单。✨

您可以使用 `@nuxt/kit` 提供的 [`addComponentsDir`](https://nuxt.zhcndoc.com/docs/3.x/api/kit/components#addcomponentsdir) 方法，在 Nuxt 模块中注册组件目录。

假设目录结构：

```bash [目录结构]
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts
```

在 `awesome-ui/nuxt.ts` 中使用 `addComponentsDir` 钩子：

```ts twoslash
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup () {
    const resolver = createResolver(import.meta.url)

    // 添加 ./components 目录
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})
```

完成！现在在项目中只需在 `nuxt.config` 文件中导入该模块：

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

……然后在 `pages/index.vue` 里直接使用库组件（带 `awesome-` 前缀）：

```vue
<template>
  <div>
    我的 <AwesomeButton>UI 按钮</AwesomeButton>！
    <awesome-alert>这是一个提示！</awesome-alert>
  </div>
</template>
```

组件仅会在使用时自动导入，并在更新 `node_modules/awesome-ui/components/` 的组件时支持热更新（HMR）。

::link-example{to="https://nuxt.zhcndoc.com/docs/examples/features/auto-imports"}
::


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
