components
Nuxt 会自动导入此目录中的任何组件(以及任何您可能使用的模块注册的组件)。
-| components/
---| AppHeader.vue
---| AppFooter.vue
<template>
<div>
<AppHeader />
<NuxtPage />
<AppFooter />
</div>
</template>
组件名称
如果您在嵌套目录中有一个组件,例如:
-| components/
---| base/
-----| foo/
-------| Button.vue
... 那么组件的名称将基于其自身的路径目录和文件名,重复的段会被移除。因此,组件的名称将是:
<BaseFooButton />
Button.vue
重命名为 BaseFooButton.vue
。如果您希望仅根据组件名称自动导入组件,而不是路径,则需要使用配置对象的扩展形式将 pathPrefix
选项设置为 false
:
export default defineNuxtConfig({
components: [
{
path: '~/components',
pathPrefix: false, // [!代码 ++]
},
],
});
这将使用与 Nuxt 2 中使用的相同策略注册组件。例如,~/components/Some/MyComponent.vue
将可用作 <MyComponent>
而不是 <SomeMyComponent>
。
动态组件
如果您希望使用 Vue 的 <component :is="someComputedComponent">
语法,则需要使用 Vue 提供的 resolveComponent
辅助函数,或者直接从 #components
导入组件并将其传递给 is
属性。
例如:
<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 包大小非常有帮助。
<script setup lang="ts">
const show = ref(false)
</script>
<template>
<div>
<h1>山脉</h1>
<LazyMountainsList v-if="show" />
<button v-if="!show" @click="show = true">显示列表</button>
</div>
</template>
延迟(或懒加载)水合
懒加载组件非常适合控制应用中的代码块大小,但它们并不总是提升运行时性能,因为它们仍然会急切加载,除非有条件地渲染。在现实世界的应用中,一些页面可能包含大量内容和组件,而大多数情况下,并不需要所有这些组件在页面加载时都是交互式的。让它们全部急切加载可能会对性能产生负面影响。
为了优化您的应用,您可能希望延迟一些组件的水合,直到它们可见,或直到浏览器完成更重要的任务。
Nuxt 支持这使用延迟(或懒加载)水合,允许您控制何时使组件变为交互式。
水合策略
Nuxt 提供了一系列内置的水合策略。每个懒加载组件只能使用一个策略。
v-bind
展开 props 对象)。它也不适用于来自 #components
的直接导入。hydrate-on-visible
当组件在视口中可见时进行水合。
<template>
<div>
<LazyMyComponent hydrate-on-visible />
</div>
</template>
hydrateOnVisible
策略。hydrate-on-idle
当浏览器处于空闲状态时进行水合。如果您需要尽快加载该组件,但不想阻塞关键渲染路径,这是合适的。
您还可以传递一个数字作为最大超时。
<template>
<div>
<LazyMyComponent hydrate-on-idle />
</div>
</template>
hydrateOnIdle
策略。hydrate-on-interaction
在特定交互后(例如,点击,鼠标悬停)进行水合。
<template>
<div>
<LazyMyComponent hydrate-on-interaction="mouseover" />
</div>
</template>
如果您未传递事件或事件列表,默认情况下在 pointerenter
和 focus
上进行水合。
hydrateOnInteraction
策略。hydrate-on-media-query
当窗口匹配媒体查询时进行水合。
<template>
<div>
<LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
</div>
</template>
hydrateOnMediaQuery
策略。hydrate-after
在指定延迟(以毫秒为单位)后进行水合。
<template>
<div>
<LazyMyComponent :hydrate-after="2000" />
</div>
</template>
hydrate-when
根据布尔条件进行水合。
<template>
<div>
<LazyMyComponent :hydrate-when="isReady" />
</div>
</template>
<script setup lang="ts">
const isReady = ref(false)
function myFunction() {
// 触发自定义水合策略...
isReady.value = true
}
</script>
hydrate-never
永远不进行水合组件。
<template>
<div>
<LazyMyComponent hydrate-never />
</div>
</template>
监听水合事件
所有延迟水合组件在进行水合时都会发出 @hydrated
事件。
<template>
<div>
<LazyMyComponent hydrate-on-visible @hydrated="onHydrate" />
</div>
</template>
<script setup lang="ts">
function onHydrate() {
console.log("组件已经被水合!")
}
</script>
注意事项和最佳实践
延迟水合可以提供性能收益,但正确使用它至关重要:
- 优先考虑视口内容: 避免对关键的、位于折叠的内容使用延迟水合。它最适合于不立即需要的内容。
- 条件渲染: 当在懒加载组件上使用
v-if="false"
时,您可能不需要延迟水合。您可以直接使用普通懒加载组件。 - 共享状态: 请注意跨多个组件的共享状态(
v-model
)。在一个组件中更新模型可能会触发与该模型绑定的所有组件的水合。 - 使用每种策略的预期用例: 每种策略都是针对特定目的进行优化的。
hydrate-when
最适合于可能不需要始终进行水合的组件。hydrate-after
适合可以等待特定时间的组件。hydrate-on-idle
适合可以在浏览器处于空闲时进行水合的组件。
- 避免在交互组件上使用
hydrate-never
: 如果组件需要用户交互,则不应设置为永远不水合。
直接导入
如果您希望显式导入组件,或者需要绕过 Nuxt 的自动导入功能,可以从 #components
显式导入组件。
<script setup lang="ts">
import { NuxtLink, LazyMountainsList } from '#components'
const show = ref(false)
</script>
<template>
<div>
<h1>山脉</h1>
<LazyMountainsList v-if="show" />
<button v-if="!show" @click="show = true">显示列表</button>
<NuxtLink to="/">首页</NuxtLink>
</div>
</template>
自定义目录
默认情况下,仅扫描 ~/components
目录。如果您希望添加其他目录,或更改在该目录子文件夹内扫描组件的方式,则可以将其他目录添加到配置中:
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' },
// 如果您希望对此进行重写,这一点很重要
// 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',
})
},
})
<template>
<div>
<!-- 该组件使用我们指定的名称并被自动导入 -->
<MyAutoImportedComponent />
</div>
</template>
组件扩展
默认情况下,任何在 nuxt.config.ts
的 extensions 键中 指定的扩展名都被视为组件。
如果您需要限制应注册为组件的文件扩展名,您可以使用组件目录声明的扩展形式以及其 extensions
键:
export default defineNuxtConfig({
components: [
{
path: '~/components',
extensions: ['.vue'], // [!代码 ++]
}
]
})
客户端组件
如果一个组件仅用于在客户端渲染,您可以在您的组件名后添加 .client
后缀。
| components/
--| Comments.client.vue
<template>
<div>
<!-- 这个组件只会在客户端渲染 -->
<Comments />
</div>
</template>
#components
导入。从其真实路径显式导入这些组件不会将其转换为仅客户端组件。.client
组件仅在挂载后渲染。要在 onMounted()
时访问渲染的模板,请在 onMounted()
钩子的回调中添加 await nextTick()
。服务器组件
服务器组件允许在客户端应用中进行单个组件的服务器渲染。即使您正在生成静态网站,也可以在 Nuxt 中使用服务器组件。这使得构建混合动态组件、服务器渲染的 HTML 甚至静态标记代码块的复杂网站成为可能。
服务器组件可以单独使用,也可以与 客户端组件 配对使用。
独立服务器组件
独立服务器组件将始终在服务器上渲染,也称为 Islands 组件。
当它们的 props 更新时,这将导致网络请求,从而在该位置更新渲染的 HTML。
服务器组件当前是实验性的,为了使用它们,您需要在 nuxt.config 中启用 'component islands' 功能:
export default defineNuxtConfig({
experimental: {
componentIslands: true
}
})
现在,您可以注册仅服务器组件,使用 .server
后缀并在应用程序中自动使用它们。
-| components/
---| HighlightedMarkdown.server.vue
<template>
<div>
<!--
这将自动在服务器上渲染,这意味着您的 markdown 解析 + 高亮库不会包含在客户端包中。
-->
<HighlightedMarkdown markdown="# 标题" />
</div>
</template>
仅服务器组件在底层使用 <NuxtIsland>
,这意味着 lazy
属性和 #fallback
插槽都向下传递。
服务器组件中的客户端组件
experimental.componentIslands.selectiveClient
设置为 true。您可以通过为希望在客户端加载的组件设置 nuxt-client
属性来部分水合组件。
<template>
<div>
<HighlightedMarkdown markdown="# 标题" />
<!-- 计数器将在客户端加载和水合 -->
<Counter nuxt-client :count="5" />
</div>
</template>
experimental.componentIsland.selectiveClient
设置为 'deep'
时有效,并且由于它们在服务器端渲染,因此它们在客户端不可交互。服务器组件上下文
在渲染服务器唯一组件或岛屿组件时,<NuxtIsland>
会发起一个 fetch 请求,该请求返回一个 NuxtIslandResponse
。(如果在服务器上渲染,这是内部请求,如果在客户端导航时渲染,则可以在网络选项卡中看到该请求。)
这意味着:
- 将在服务器端创建一个新的 Vue 应用程序,以创建
NuxtIslandResponse
。 - 渲染组件时将创建一个新的 "island context"。
- 您无法从应用程序的其余部分访问 "island context",也无法从 island 组件访问应用程序其余部分的上下文。换句话说,服务器组件或岛屿是与应用程序的其余部分 隔离 的。
- 当渲染 island 时,您的插件将再次运行,除非它们设置了
env: { islands: false }
(您可以在对象语法插件中做到这一点)。
在 island 组件中,您可以通过 nuxtApp.ssrContext.islandContext
访问其 island 上下文。请注意,尽管 island 组件仍被标记为实验性,但此上下文的格式可能会发生变化。
display: contents;
的 <div>
中。与客户端组件配对
在这种情况下,.server
+ .client
组件是组件的两个“部分”,可以用于服务器和客户端在组件的独立实现的高级用例。
-| components/
---| Comments.client.vue
---| Comments.server.vue
<template>
<div>
<!-- 该组件将在服务器上渲染 Comments.server,然后在浏览器中挂载后渲染 Comments.client -->
<Comments />
</div>
</template>
内置 Nuxt 组件
Nuxt 提供了一些组件,包括 <ClientOnly>
和 <DevOnly>
。您可以在 API 文档中阅读更多有关它们的信息。
库作者
创建带有自动树摇动和组件注册的 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'
})
}
}
})
就是这样!现在在您的项目中,您可以在 nuxt.config
文件中将您的 UI 库作为 Nuxt 模块导入:
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/
中的组件时支持热更新。