Nuxt3 中文课程 《实战全栈开发简书》.

components

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

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

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

例如:

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 选项也可以针对每个组件目录进行设置。

动态导入

要动态导入组件(也称为延迟加载组件),您只需将 Lazy 前缀添加到组件的名称中。如果组件不总是需要,这特别有用。

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

pages/index.vue
<script setup>
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 的自动导入功能,您还可以从 #components 直接导入组件。

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

    // 如果您想要覆盖 `~/components` 的子目录,请确保它是最后一个。
    //
    // ~/components/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components'
  ]
})
任何嵌套的目录都需要首先添加,因为它们按顺序扫描。

组件扩展名

默认情况下,任何具有在 nuxt.config.ts 的 extensions 键中指定的扩展名 的文件都被视为组件。 如果您需要限制应注册为组件的文件扩展名,可以使用组件目录声明的扩展名键的扩展形式:

export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
+     extensions: ['.vue'],
    }
  ]
})

客户端组件

如果一个组件只在客户端渲染,您可以在组件名后添加 .client 后缀。

目录结构
| components/
--| Comments.client.vue
pages/example.vue
<template>
  <div>
    <!-- 此组件仅在客户端渲染 -->
    <Comments />
  </div>
</template>
此功能仅适用于 Nuxt 的自动导入和 #components 导入。从实际路径显式导入这些组件不会将它们转换为仅客户端组件。
.client 组件仅在挂载后才会渲染。要在 onMounted() 的回调中访问渲染的模板,请在 onMounted() 钩子的回调中添加 await nextTick()

服务器端组件

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

即使在服务器端渲染上,服务器端组件也可以单独使用或与客户端组件配对使用。

观看有关 Nuxt 服务器端组件的视频。
阅读 Daniel Roe 关于 Nuxt 服务器端组件的指南。

独立服务器端组件

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

当其 props 更新时,这将导致网络请求,以原地更新渲染的 HTML。

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

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

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

目录结构
| components/
--| HighlightedMarkdown.server.vue
pages/example.vue
<template>
  <div>
    <!--
      这将自动在服务器上渲染,意味着您的 markdown 解析和高亮
      库不会包含在客户端捆绑包中。
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

仅服务器端组件在幕后使用了 <NuxtIsland>,这意味着 lazy 属性和 #fallback 插槽都传递给它。

服务器端组件上下文

在渲染服务器端组件或岛屿组件时,<NuxtIsland> 进行了一个提取请求,该请求返回一个 NuxtIslandResponse。(如果在服务器上渲染,则为内部请求,如果在客户端导航时渲染,则为可以在网络选项卡中查看的请求。)

这意味着:

  • 将在服务器端创建一个新的 Vue 应用程序以创建 NuxtIslandResponse
  • 在渲染组件时,将创建一个新的“岛屿上下文”。
  • 您无法从应用程序的其余部分访问“岛屿上下文”,也无法从岛屿组件中访问应用程序的其余部分的上下文。换句话说,服务器端组件或岛屿与应用程序的其余部分是_隔离_的。
  • 在岛屿组件中,您可以通过 nuxtApp.ssrContext.islandContext 访问岛屿上下文。请注意,虽然岛屿组件仍然标记为实验性的,但此上下文的格式可能会发生变化。
  • 插槽可以是交互式的,并且在 <div> 中使用 display: contents; 进行包装。 ::

配对的客户端组件

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

目录结构
| components/
--| Comments.client.vue
--| Comments.server.vue
pages/example.vue
<template>
  <div>
    <!-- 这个组件将在服务器端渲染 Comments.server,然后在浏览器中挂载后渲染 Comments.client -->
    <Comments />
  </div>
</template>

<ClientOnly> 组件

Nuxt 提供了 <ClientOnly> 组件,用于仅在客户端渲染组件。

pages/example.vue
<template>
  <div>
    <Sidebar />
    <ClientOnly>
      <!-- 此组件仅在客户端渲染 -->
      <Comments />
    </ClientOnly>
  </div>
</template>

<ClientOnly> 在客户端渲染之前,使用插槽作为回退。

pages/example.vue
<template>
  <div>
    <Sidebar />
    <!-- 这将在服务器端渲染 "span" 元素 -->
    <ClientOnly fallbackTag="span">
      <!-- 此组件仅在客户端渲染 -->
      <Comments />
      <template #fallback>
        <!-- 这将在服务器端渲染 -->
        <p>Loading comments...</p>
      </template>
    </ClientOnly>
  </div>
</template>

<DevOnly> 组件

Nuxt 提供了 <DevOnly> 组件,仅在开发环境中渲染组件。

该内容不会包含在生产构建中,并进行了 tree-shaking。

pages/example.vue
<template>
  <div>
    <Sidebar />
    <DevOnly>
      <!-- 仅在开发环境中渲染此组件 -->
      <LazyDebugBar />

      <!-- 如果您需要在生产环境中进行替换 -->
      <!-- 请确保使用 `nuxt preview` 进行测试 -->
      <template #fallback>
        <div><!-- 用于 flex.justify-between 的空 div --></div>
      </template>
    </DevOnly>
  </div>
</template>

<NuxtClientFallback> 组件

Nuxt 提供了 <NuxtClientFallback> 组件,如果其任何子组件在 SSR 中触发错误,则在客户端渲染其内容。

您可以指定 fallbackTag,以便在服务器端无法渲染时渲染特定的标记。

pages/example.vue
<template>
  <div>
    <Sidebar />
    <!-- 此组件将在客户端渲染 -->
    <NuxtClientFallback fallback-tag="span">
      <Comments />
      <BrokeInSSR />
    </NuxtClientFallback>
  </div>
</template>

库作者

使用自动摇树和组件注册创建 Vue 组件库非常简单 ✨

您可以使用 components:dirs 钩子来扩展目录列表,而无需在您的 Nuxt 模块中要求用户配置。

假设有以下目录结构:

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

然后在 awesome-ui/nuxt.js 中,您可以使用 components:dirs 钩子:

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

export default defineNuxtModule({
  hooks: {
    'components:dirs': (dirs) => {
      const { resolve } = createResolver(import.meta.url)
      // 将 ./components 目录添加到列表中
      dirs.push({
        path: resolve('./components'),
        prefix: 'awesome'
      })
    }
  }
})

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

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

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

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

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

Read and edit a live example in Docs > Examples > Features > Auto Imports.