components

components/ 目录用于存放你所有的 Vue 组件。

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

目录结构
-| components/
---| AppHeader.vue
---| AppFooter.vue
app/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 prop。

例如

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>
如果你使用 resolveComponent 来处理动态组件,请确保只插入组件名称,它必须是字面字符串,不能是或包含变量。该字符串在编译步骤中进行静态分析。

或者,虽然不推荐,你可以全局注册所有组件,这将为所有组件创建异步块,并使其在整个应用程序中可用。

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

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

global 选项也可以按组件目录设置。

动态导入

要动态导入组件(也称为组件的惰性加载),你只需在组件名称前添加 Lazy 前缀。如果组件并非总是需要,这尤其有用。

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

app/pages/index.vue
<script setup lang="ts">
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>

延迟(或惰性)hydration

惰性组件对于控制应用程序的块大小很有用,但它们并不总是能提高运行时性能,因为除非条件渲染,否则它们仍然会主动加载。在实际应用程序中,有些页面可能包含大量内容和组件,而且大多数情况下,并非所有组件都需要在页面加载后立即具有交互性。让它们都主动加载可能会对性能产生负面影响。

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

Nuxt 支持使用惰性(或延迟)hydration,允许你控制组件何时变得可交互。

Hydration 策略

Nuxt 提供了一系列内置的 hydration 策略。每个惰性组件只能使用一种策略。

懒惰 hydration 组件上的任何 prop 更改都会立即触发 hydration。(例如,更改带有 hydrate-never 的组件上的 prop 会导致它进行 hydration)
目前 Nuxt 的内置惰性 hydration 仅适用于单文件组件 (SFC),并且要求你在模板中定义 prop(而不是通过 v-bind 展开 prop 对象)。它也不适用于从 #components 直接导入。

hydrate-on-visible

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

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

hydrate-on-idle

当浏览器空闲时进行 hydration。这适用于你需要组件尽快加载但又不阻塞关键渲染路径的情况。

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

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

hydrate-on-interaction

在指定交互(例如,点击、鼠标悬停)后进行 hydration。

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

如果你不传递事件或事件列表,它将默认在 pointerenterclickfocus 时进行 hydration。

在底层,这使用了 Vue 的内置hydrateOnInteraction 策略.

hydrate-on-media-query

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

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

hydrate-after

在指定延迟(毫秒)后进行 hydration。

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

hydrate-when

根据布尔条件进行 hydration。

app/pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-when="isReady" />
  </div>
</template>

<script setup lang="ts">
const isReady = ref(false)
function myFunction () {
  // trigger custom hydration strategy...
  isReady.value = true
}
</script>

hydrate-never

从不进行组件 hydration。

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

监听 hydration 事件

所有延迟 hydration 组件在 hydration 时都会发出一个 @hydrated 事件。

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

<script setup lang="ts">
function onHydrate () {
  console.log('Component has been hydrated!')
}
</script>

注意事项和最佳实践

延迟 hydration 可以提供性能优势,但正确使用它至关重要

  1. 优先处理视口内内容: 避免对关键的、首屏内容进行延迟 hydration。它最适合不需要立即使用的内容。
  2. 条件渲染: 在惰性组件上使用 v-if="false" 时,你可能不需要延迟 hydration。你可以只使用普通的惰性组件。
  3. 共享状态: 注意多个组件之间的共享状态 (v-model)。在一个组件中更新模型可以触发绑定到该模型的所有组件的 hydration。
  4. 使用每种策略的预期用途: 每种策略都针对特定目的进行了优化。
    • hydrate-when 最适合可能不总是需要 hydration 的组件。
    • hydrate-after 适用于可以等待特定时间的组件。
    • hydrate-on-idle 适用于浏览器空闲时可以 hydration 的组件。
  5. 避免在交互式组件上使用 hydrate-never 如果组件需要用户交互,则不应设置为从不进行 hydration。

直接导入

如果你想或需要绕过 Nuxt 的自动导入功能,你还可以从 #components 显式导入组件。

app/pages/index.vue
<script setup lang="ts">
import { LazyMountainsList, NuxtLink } 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>

自定义目录

默认情况下,只扫描 ~/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' },

    // It's important that this comes last if you have overrides you wish to apply
    // to 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
app/pages/example.vue
<template>
  <div>
    <!-- this component will only be rendered on client side -->
    <Comments />
  </div>
</template>
此功能仅适用于 Nuxt 自动导入和 #components 导入。从其真实路径显式导入这些组件不会将它们转换为仅客户端组件。
.client 组件只在挂载后渲染。要使用 onMounted() 访问渲染的模板,请在 onMounted() 钩子的回调中添加 await nextTick()
你也可以使用 <ClientOnly> 组件实现类似的结果。

服务器组件

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

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

阅读 Daniel Roe 关于 Nuxt 服务器组件的指南。

独立服务器组件

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

当它们的 props 更新时,这将导致网络请求,并就地更新渲染的 HTML。

服务器组件目前处于实验阶段,为了使用它们,你需要在 nuxt.config 中启用“组件岛”功能

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

现在你可以使用 .server 后缀注册仅限服务器的组件,并在应用程序中的任何位置自动使用它们。

目录结构
-| components/
---| HighlightedMarkdown.server.vue
app/pages/example.vue
<template>
  <div>
    <!--
      this will automatically be rendered on the server, meaning your markdown parsing + highlighting
      libraries are not included in your client bundle.
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

仅限服务器的组件在底层使用 <NuxtIsland>,这意味着 lazy prop 和 #fallback slot 都会传递给它。

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

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

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

你可以通过在希望在客户端加载的组件上设置 nuxt-client 属性来部分 hydration 组件。

app/components/ServerWithClient.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
    <!-- Counter will be loaded and hydrated client-side -->
    <Counter
      nuxt-client
      :count="5"
    />
  </div>
</template>
这只在服务器组件中有效。客户端组件的插槽仅在 experimental.componentIsland.selectiveClient 设置为 'deep' 时才有效,并且由于它们是在服务器端渲染的,因此在客户端不再具有交互性。

服务器组件上下文

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

这意味着

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

在 island 组件中,你可以通过 nuxtApp.ssrContext.islandContext 访问其 island 上下文。请注意,虽然 island 组件仍被标记为实验性,但此上下文的格式可能会更改。

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

与客户端组件配对

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

目录结构
-| components/
---| Comments.client.vue
---| Comments.server.vue
app/pages/example.vue
<template>
  <div>
    <!-- this component will render Comments.server on the server then Comments.client once mounted in the browser -->
    <Comments />
  </div>
</template>

内置 Nuxt 组件

Nuxt 提供了许多组件,包括 <ClientOnly><DevOnly>。你可以在 API 文档中了解更多信息。

阅读更多内容,请访问文档 > 4 X > API

库作者

使用自动 tree-shaking 和组件注册制作 Vue 组件库非常容易。✨

你可以使用 @nuxt/kit 提供的 addComponentsDir 方法在你的 Nuxt 模块中注册你的组件目录。

设想一个这样的目录结构

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

然后,在 awesome-ui/nuxt.ts 中,你可以使用 addComponentsDir 钩子

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

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

    // Add ./components dir to the list
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})

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

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

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

<template>
  <div>
    My <AwesomeButton>UI button</AwesomeButton>!
    <awesome-alert>Here's an alert!</awesome-alert>
  </div>
</template>

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

文档 > 4.X > 示例 > 功能 > 自动导入 中阅读并编辑实时示例。