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 来处理动态组件,请确保只插入组件的名称,该名称必须是字面量字符串,不能是变量或包含变量。该字符串在编译阶段进行静态分析。

或者,虽然不推荐,你也可以全局注册所有组件,这会为你的所有组件创建异步代码块(async chunks),并使它们在整个应用程序中可用。

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

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

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),直到它们可见,或者直到浏览器完成了更重要的任务。

Nuxt 支持使用延迟(或懒)水合作用,让你能够控制组件何时变得可交互。

水合作用策略

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

对懒水合组件的任何 prop 更改都会立即触发水合作用。(例如,更改具有 hydrate-never 的组件上的 prop 将导致其进行水合作用)
目前,Nuxt 的内置懒水合作用仅适用于单文件组件 (SFC),并且要求你在模板中显式定义 prop(而不是通过 v-bind 展开 prop 对象)。它也不适用于直接从 #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

在特定交互(例如 click、mouseover)发生后对组件进行水合作用。

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.ts 的 extensions 键中指定的扩展名的文件都会被视为组件。如果你需要限制应注册为组件的文件扩展名,可以使用组件目录声明的扩展形式及其 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 中启用 '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 注释也被视为元素。)
Props 通过 URL 查询参数传递给服务器组件,因此受限于 URL 的可能长度,请小心不要通过 props 向服务器组件传递过大的数据量。
当在其他 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> 会发起一个 fetch 请求,并返回一个 NuxtIslandResponse。(如果是在服务器端渲染,这是一个内部请求;或者如果是客户端导航渲染,你可以在网络选项卡中看到该请求。)

这意味着

  • 将会在服务器端创建一个新的 Vue 应用来生成 NuxtIslandResponse
  • 在渲染组件时,将创建一个新的 'island 上下文'。
  • 你不能从应用程序的其余部分访问该 'island 上下文',也不能从该 island 组件中访问应用程序其余部分的上下文。换句话说,服务器组件或 island 与你的应用程序其余部分是隔离的。
  • 除非插件设置了 env: { islands: false }(可以在对象语法的插件中完成),否则你的插件在渲染 island 时会再次运行。
路由中间件在渲染 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

库作者

制作带有自动 Tree-shaking 和组件注册的 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 > 示例 > 功能 > 自动导入 中阅读并编辑实时示例。