components

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

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

¥Nuxt automatically imports any components in this directory (along with components that are registered by any modules you may be using).

Directory Structure
-| components/
---| AppHeader.vue
---| AppFooter.vue
app.vue
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

组件名称

¥Component Names

如果你的组件位于嵌套目录中,例如:

¥If you have a component in nested directories such as:

Directory Structure
-| components/
---| base/
-----| foo/
-------| Button.vue

...则组件的名称将基于其自身的路径目录和文件名,并删除重复的片段。因此,组件的名称将为:

¥... then the component's name will be based on its own path directory and filename, with duplicate segments being removed. Therefore, the component's name will be:

<BaseFooButton />
为了清晰起见,我们建议组件的文件名与其名称一致。因此,在上面的示例中,你可以将 Button.vue 重命名为 BaseFooButton.vue

如果你想仅根据组件名称(而非路径)自动导入组件,则需要使用配置对象的扩展形式将 pathPrefix 选项设置为 false

¥If you want to auto-import components based only on its name, not path, then you need to set pathPrefix option to false using extended form of the configuration object:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      pathPrefix: false,    },
  ],
});

此方法使用与 Nuxt 2 中相同的策略注册组件。例如,~/components/Some/MyComponent.vue 可以用作 <MyComponent>,而不能用作 <SomeMyComponent>

¥This registers the components using the same strategy as used in Nuxt 2. For example, ~/components/Some/MyComponent.vue will be usable as <MyComponent> and not <SomeMyComponent>.

动态组件

¥Dynamic Components

如果你想使用 Vue <component :is="someComputedComponent"> 语法,则需要使用 Vue 提供的 resolveComponent 辅助函数,或者直接从 #components 导入组件并将其传递给 is 属性。

¥If you want to use the Vue <component :is="someComputedComponent"> syntax, you need to use the resolveComponent helper provided by Vue or import the component directly from #components and pass it into is prop.

例如:

¥For example:

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 处理动态组件,请确保除了组件名称之外不要插入任何其他内容。组件名称必须是文字字符串,并且不能是或包含变量。该字符串在编译阶段会被静态分析。

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

¥Alternatively, though not recommended, you can register all your components globally, which will create async chunks for all your components and make them available throughout your application.

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

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

¥You can also selectively register some components globally by placing them in a ~/components/global directory, or by using a .global.vue suffix in the filename. As noted above, each global component is rendered in a separate chunk, so be careful not to overuse this feature.

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

动态导入

¥Dynamic Imports

要动态导入组件(也称为延迟加载组件),只需在组件名称中添加 Lazy 前缀即可。如果组件并非总是需要,则尤其有用。

¥To dynamically import a component (also known as lazy-loading a component) all you need to do is add the Lazy prefix to the component's name. This is particularly useful if the component is not always needed.

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

¥By using the Lazy prefix you can delay loading the component code until the right moment, which can be helpful for optimizing your JavaScript bundle size.

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>

延迟加载(或惰性加载)

¥Delayed (or Lazy) Hydration

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

¥Lazy components are great for controlling the chunk sizes in your app, but they don't always enhance runtime performance, as they still load eagerly unless conditionally rendered. In real-world applications, some pages may include a lot of content and a lot of components, and most of the time not all of them need to be interactive as soon as the page is loaded. Having them all load eagerly can negatively impact performance.

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

¥In order to optimize your app, you may want to delay the hydration of some components until they're visible, or until the browser is done with more important tasks.

Nuxt 使用延迟加载 (Lazy Hydration) 来实现此功能,允许你控制组件何时进行交互。

¥Nuxt supports this using lazy (or delayed) hydration, allowing you to control when components become interactive.

混合策略

¥Hydration Strategies

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

¥Nuxt provides a range of built-in hydration strategies. Only one strategy can be used per lazy component.

目前,Nuxt 内置的惰性加载功能仅适用于单文件组件 (SFC),并且需要你在模板中定义 prop(而不是通过 v-bind 扩展 props 对象)。它也不适用于从 #components 直接导入。¥Currently Nuxt's built-in lazy hydration only works in single-file components (SFCs), and requires you to define the prop in the template (rather than spreading an object of props via v-bind). It also does not work with direct imports from #components.

hydrate-on-visible

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

¥Hydrates the component when it becomes visible in the viewport.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>

:

Read more in IntersectionObserver options.

了解更多关于 hydrate-on-visible 选项的信息。

¥Read more about the options for hydrate-on-visible.

::

内部使用了 Vue 内置的 hydrateOnVisible strategy

hydrate-on-idle

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

¥Hydrates the component when the browser is idle. This is suitable if you need the component to load as soon as possible, but not block the critical rendering path.

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

¥You can also pass a number which serves as a max timeout.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-idle />
  </div>
</template>
内部使用了 Vue 内置的 hydrateOnIdle strategy

hydrate-on-interaction

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

¥Hydrates the component after a specified interaction (e.g., click, mouseover).

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

如果你未传递事件或事件列表,则默认在 pointerenterfocus 上进行 hydrate 操作。

¥If you do not pass an event or list of events, it defaults to hydrating on pointerenter and focus.

内部使用了 Vue 内置的 hydrateOnInteraction strategy

hydrate-on-media-query

当窗口匹配媒体查询时,对其进行水合操作。

¥Hydrates the component when the window matches a media query.

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
  </div>
</template>
内部使用了 Vue 内置的 hydrateOnMediaQuery strategy

hydrate-after

在指定的延迟(以毫秒为单位)后对组件进行混合。

¥Hydrates the component after a specified delay (in milliseconds).

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

hydrate-when

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

¥Hydrates the component based on a boolean condition.

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

永不水合组件。

¥Never hydrates the component.

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

监听 Hydration 事件

¥Listening to Hydration Events

所有延迟加载组件在加载时都会发出 @hydrated 事件。

¥All delayed hydration components emit a @hydrated event when they are hydrated.

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>

注意事项和最佳实践

¥Caveats and Best Practices

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

¥Delayed hydration can offer performance benefits, but it's essential to use it correctly:

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

直接导入

¥Direct Imports

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

¥You can also explicitly import components from #components if you want or need to bypass Nuxt's auto-importing functionality.

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>

自定义目录

¥Custom Directories

默认情况下,仅扫描 ~/components 目录。如果你想添加其他目录,或更改此目录子文件夹中组件的扫描方式,你可以在配置中添加其他目录:

¥By default, only the ~/components directory is scanned. If you want to add other directories, or change how the components are scanned within a subfolder of this directory, you can add additional directories to the configuration:

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 Packages

如果你想从 npm 包自动导入组件,你可以在 本地模块 中使用 addComponent 来注册它们。

¥If you want to auto-import components from an npm package, you can use addComponent in a local module to register them.

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',
    })
  },
})

组件扩展

¥Component Extensions

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

¥By default, any file with an extension specified in the extensions key of nuxt.config.ts is treated as a component. If you need to restrict the file extensions that should be registered as components, you can use the extended form of the components directory declaration and its extensions key:

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

客户端组件

¥Client Components

如果组件仅在客户端渲染,则可以为组件添加 .client 后缀。

¥If a component is meant to be rendered only client-side, you can add the .client suffix to your component.

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

:

Read more in Docs > API > Components > Client Only.

你也可以使用 <ClientOnly> 组件实现类似的结果。

¥You can also achieve a similar result with the <ClientOnly> component.

::

服务器组件

¥Server Components

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

¥Server components allow server-rendering individual components within your client-side apps. It's possible to use server components within Nuxt, even if you are generating a static site. That makes it possible to build complex sites that mix dynamic components, server-rendered HTML and even static chunks of markup.

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

¥Server components can either be used on their own or paired with a client component.

阅读 Daniel Roe 的 Nuxt 服务器组件指南。¥Read Daniel Roe's guide to Nuxt Server Components.

独立服务器组件

¥Standalone server components

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

¥Standalone server components will always be rendered on the server, also known as Islands components.

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

¥When their props update, this will result in a network request that will update the rendered HTML in-place.

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

¥Server components are currently experimental and in order to use them, you need to enable the 'component islands' feature in your nuxt.config:

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

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

¥Now you can register server-only components with the .server suffix and use them anywhere in your application automatically.

Directory Structure
-| components/
---| HighlightedMarkdown.server.vue
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 属性都会传递给它。

¥Server-only components use <NuxtIsland> under the hood, meaning that lazy prop and #fallback slot are both passed down to it.

服务器组件(和孤岛)必须有一个根元素。(HTML 注释也被视为元素。)¥Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)
Props 通过 URL 查询参数传递给服务器组件,因此受 URL 长度的限制,因此请注意不要通过 Props 向服务器组件传递大量数据。¥Props are passed to server components via URL query parameters, and are therefore limited by the possible length of a URL, so be careful not to pass enormous amounts of data to server components via props.
在将岛嵌套在其他岛中时要小心,因为每个岛都会增加一些额外的开销。¥Be careful when nesting islands within other islands as each island adds some extra overhead.
大多数服务器组件和孤岛组件(例如插槽和客户端组件)的功能仅适用于单文件组件。¥Most features for server-only components and island components, such as slots and client components, are only available for single file components.

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

¥Client components within server components

此功能需要你的配置中包含 experimental.componentIslands.selectiveClient 才能启用。

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

¥You can partially hydrate a component by setting a nuxt-client attribute on the component you wish to be loaded client-side.

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' 时才有效,并且由于它们是在服务器端渲染的,因此一旦进入客户端就无法交互。

服务器组件上下文

¥Server Component Context

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

¥When rendering a server-only or island component, <NuxtIsland> makes a fetch request which comes back with a NuxtIslandResponse. (This is an internal request if rendered on the server, or a request that you can see in the network tab if it's rendering on client-side navigation.)

这意味着:

¥This means:

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

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

¥Within an island component, you can access its island context through nuxtApp.ssrContext.islandContext. Note that while island components are still marked as experimental, the format of this context may change.

插槽可以交互,并被封装在带有 display: contents;<div> 中。

与客户端组件配对

¥Paired with a Client component

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

¥In this case, the .server + .client components are two 'halves' of a component and can be used in advanced use cases for separate implementations of a component on server and client side.

Directory Structure
-| components/
---| Comments.client.vue
---| Comments.server.vue
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 组件

¥Built-In Nuxt Components

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

¥There are a number of components that Nuxt provides, including <ClientOnly> and <DevOnly>. You can read more about them in the API documentation.

:

Read more in Docs > API.

::

库作者

¥Library Authors

创建具有自动 tree-shaking 和组件注册功能的 Vue 组件库非常简单。✨

¥Making Vue component libraries with automatic tree-shaking and component registration is super easy. ✨

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

¥You can use the addComponentsDir method provided from the @nuxt/kit to register your components directory in your Nuxt module.

设想一个这样的目录结构:

¥Imagine a directory structure like this:

Directory Structure
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts

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

¥Then in awesome-ui/nuxt.ts you can use the addComponentsDir hook:

import { createResolver, defineNuxtModule, addComponentsDir } 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',
    })
  },
})

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

¥That's it! Now in your project, you can import your UI library as a Nuxt module in your nuxt.config file:

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

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

¥... and directly use the module components (prefixed with awesome-) in our pages/index.vue:

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

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

¥It will automatically import the components only if used and also support HMR when updating your components in node_modules/awesome-ui/components/.

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