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

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

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

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

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

## 组件命名

如果你的组件位于嵌套目录中，例如：

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

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

```html
<BaseFooButton />
```

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

如果你想在不影响组件名称的情况下，将组件分组到一个目录中，可以使用圆括号 `(` `)` 来命名分组目录。

```bash [Grouping Directory]
-| components/
---| base/
-----| (foo)/
-------| Button.vue
```

……那么组件名称将跳过分组目录，结果为：

```html
<BaseButton />
```

如果你只想根据组件名称而不是路径自动导入组件，那么需要使用扩展形式的配置对象，将 `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 [app/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` 后缀，来有选择地将某些组件注册为全局组件。如上所述，每个全局组件都会被渲染为单独的 chunk，所以请谨慎使用此功能。

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

## 动态导入

要动态导入组件（也称为懒加载组件），只需在组件名称前添加 `Lazy` 前缀。当组件并非始终需要时，这种方式尤其有用。

使用 `Lazy` 前缀，可以将组件代码的加载延迟到适当的时间，从而帮助优化 JavaScript 包的大小。

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

## 延迟（或懒）水合

懒加载组件有助于控制应用中的 chunk 大小，但它们并不总是能提升运行时性能，因为除非条件渲染，它们仍会被主动加载。在真实应用中，一些页面可能包含大量内容和组件，而大多数情况下并非所有组件在页面加载时都需要交互。让它们全部主动加载可能会对性能产生负面影响。

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

Nuxt 支持使用懒（或延迟）水合，让你可以控制组件何时变为可交互状态。

### 水合策略

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

::note
对懒加载水合组件的任何 prop 更改都会立即触发水合。（例如，对具有 `hydrate-never` 的组件更改 prop 将导致其水合）
::

::warning
目前 Nuxt 的内置懒加载水合仅在单文件组件（SFC）中工作，并且要求你在模板中定义 prop（而不是通过 `v-bind` 展开一个 props 对象）。它也不适用于从 `#components` 的直接导入。
::

#### `hydrate-on-visible`

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

```vue [app/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://vue.zhcndoc.com/guide/components/async#hydrate-on-visible)。
::

#### `hydrate-on-idle`

当浏览器空闲时对组件进行水合。如果你需要组件尽快加载，但不阻塞关键渲染路径，这是合适的选择。

你也可以传递一个数字，作为最大超时。

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

::note
在内部，这使用了 Vue 的内置 [`hydrateOnIdle` 策略](https://vue.zhcndoc.com/guide/components/async#hydrate-on-idle)。
::

#### `hydrate-on-interaction`

在指定的交互（例如点击、鼠标悬停）后对组件进行水合。

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

如果你没有传递事件或事件列表，则默认为在 `pointerenter`、`click` 和 `focus` 时水合。

::note
在内部，这使用了 Vue 的内置 [`hydrateOnInteraction` 策略](https://vue.zhcndoc.com/guide/components/async#hydrate-on-interaction)。
::

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

当窗口匹配某个媒体查询时对组件进行水合。

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

::note
在内部，这使用了 Vue 的内置 [`hydrateOnMediaQuery` 策略](https://vue.zhcndoc.com/guide/components/async#hydrate-on-media-query)。
::

#### `hydrate-after`

在指定的延迟（以毫秒为单位）之后对组件进行水合。

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

#### `hydrate-when`

根据布尔条件对组件进行水合。

```vue [app/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 [app/pages/index.vue]
<template>
  <div>
    <LazyMyComponent hydrate-never />
  </div>
</template>
```

### 监听水合事件

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

```vue [app/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 [app/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/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components',
  ],
})
```

::note
任何嵌套目录需要优先添加，因为它们是按顺序扫描的。
::

每个目录条目也接受 `pattern` 和 `ignore` glob 选项，它们用于控制在 `path` 中扫描哪些文件。当你的组件位于不遵循默认布局的嵌套文件夹中时，这很有用，例如一种领域驱动的结构：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  components: [
    // ~/domains/blog/components/PostCard.vue => <PostCard />
    {
      path: '~/domains',
      pattern: '*/components/**',
      pathPrefix: false,
    },
  ],
})
```

::note
如果指定了 `pattern`，`extensions` 选项将不起作用，因此请确保你的 pattern 能匹配你想要扫描的文件扩展名。
::

## npm 包

如果你想从 npm 包自动导入组件，可以在[本地模块](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/modules)中使用 [`addComponent`](https://nuxt.zhcndoc.com/docs/4.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/app.vue]
<template>
  <div>
    <!-- 该组件使用我们指定的名字，并自动导入 -->
    <MyAutoImportedComponent />
  </div>
</template>
```

\:::

## 组件扩展名

默认情况下，[`nuxt.config.ts` 的 `extensions` 键](https://nuxt.zhcndoc.com/docs/4.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 [app/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/4.x/api/components/client-only"}
  你也可以使用 `<ClientOnly>` 组件实现类似的效果。
  :::

## 服务端组件

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

服务端组件可以单独使用，也可以与[客户端组件](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/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。

使用 `.server` 后缀注册仅在服务器端运行的组件，即可在应用中的任何位置自动使用它们。

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

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

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

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

  :::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/server-components"}
  在专门的服务端组件指南中，进一步了解 islands 的渲染方式、隔离的 island 上下文、使用 `nuxt-client` 的选择性水合、插槽、缓存以及当前的限制。
  :::

### 与客户端组件配对

在这种情况下，`.server` + `.client` 组件是组件的两个 “半边”，可用于在服务器端和客户端分别实现组件的高级用例。

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

```vue [app/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/4.x/api"}
  :::

## 库作者

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

你可以使用 `@nuxt/kit` 提供的 [`addComponentsDir`](https://nuxt.zhcndoc.com/docs/4.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` 文件中将你的 UI 库作为 Nuxt 模块导入：

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

…… 并在 `app/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.
