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 属性。

例如

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>

延迟(或懒加载)水合

懒加载组件非常适合控制应用中的块大小,但它们并不总是能提高运行时性能,因为除非有条件地渲染,否则它们仍然会急切加载。在实际应用中,某些页面可能包含大量内容和大量组件,而且大多数时候并非所有组件都需要在页面加载后立即具有交互性。让它们全部急切加载会对性能产生负面影响。

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

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

水合策略

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

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

hydrate-on-visible

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

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

hydrate-on-idle

在浏览器空闲时对组件进行水合。如果需要组件尽快加载,但又不应阻塞关键渲染路径,则此方法适用。

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

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

hydrate-on-interaction

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

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

如果您未传递事件或事件列表,则默认在 pointerenterclickfocus 上进行水合。

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

hydrate-on-media-query

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

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

hydrate-after

在指定的延迟(毫秒)后对组件进行水合。

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

hydrate-when

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

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

从不水合组件。

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

监听水合事件

所有延迟水合的组件在水合时都会发出一个 @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>

注意事项和最佳实践

延迟水合可以带来性能优势,但正确使用它至关重要

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

直接导入

如果您想或需要绕过 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 组件。

当它们的属性更新时,这将导致一个网络请求,该请求将就地更新渲染的 HTML。

服务端组件目前仍处于实验阶段,要使用它们,您需要在 nuxt.config 中启用 'component islands' 功能

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 属性和 #fallback 插槽都会向下传递给它。

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

服务端组件内的客户端组件

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

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

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> 会发起一个获取请求,该请求会返回一个 NuxtIslandResponse。(如果是在服务器上渲染,这是一个内部请求;如果是客户端导航渲染,您可以在网络选项卡中看到一个请求。)

这意味着

  • 将在服务器端创建一个新的 Vue 应用来创建 NuxtIslandResponse
  • 渲染组件时会创建一个新的“Island 上下文”。
  • 您无法从应用的其余部分访问“Island 上下文”,也无法从 Island 组件访问应用其余部分的上下文。换句话说,服务端组件或 Island 与应用的其他部分是*隔离*的。
  • 您的插件将在渲染 Island 时再次运行,除非它们设置了 env: { islands: false }(您可以在对象语法的插件中执行此操作)。
路由中间件在渲染 Island 组件时不会运行。中间件是一种适用于页面而非组件的路由概念,其目的不是控制组件的渲染。

在 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 中阅读更多内容。

库作者

使用自动树摇和组件注册来制作 Vue 组件库超级简单。✨

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

想象一下这样的目录结构

目录结构
-| 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 > 示例 > 功能 > 自动导入 中阅读并编辑实时示例。