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' },
// 如果你希望对 `~/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
键:
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 甚至静态标记块。
服务器组件可以单独使用,也可以与客户端组件配对使用。
独立服务器组件
独立服务器组件将始终在服务端渲染,也称为岛式组件。
当它们的 props 更新时,将触发网络请求,在原地更新渲染的 HTML。
服务器组件目前是实验性功能,要使用它们,你需要在 nuxt.config
中启用“组件岛”功能:
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 将在客户端加载和水合 -->
<Counter nuxt-client :count="5" />
</div>
</template>
experimental.componentIsland.selectiveClient
设置为 'deep'
时有效,并且由于它们在服务端渲染,因此在客户端不具备交互性。服务器组件上下文
在渲染仅服务端或岛式组件时,<NuxtIsland>
会发起一个 fetch 请求,返回一个 NuxtIslandResponse
。(如果在服务端渲染,这是内部请求;如果在客户端导航渲染,你可以在网络标签中看到该请求。)
这意味着:
- 服务端将创建一个新的 Vue 应用来生成
NuxtIslandResponse
。 - 在渲染组件时,将创建一个新的“岛式上下文”。
- 你无法从应用的其余部分访问“岛式上下文”,也无法从岛式组件访问应用的其余上下文。换句话说,服务器组件或岛式组件与应用的其余部分是_隔离的_。
- 除非插件设置了
env: { islands: false }
(你可以在对象语法的插件中这样做),否则在渲染岛式组件时,插件会再次运行。
在岛式组件中,你可以通过 nuxtApp.ssrContext.islandContext
访问其岛式上下文。请注意,在岛式组件仍标记为实验性功能时,此上下文的格式可能会发生变化。
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 组件库非常简单。✨
你可以使用 @nuxt/kit
提供的 addComponentsDir
方法,在你的 Nuxt 模块中注册组件目录。
设想如下目录结构:
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts
然后,在 awesome-ui/nuxt.ts
中,你可以使用 addComponentsDir
钩子:
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',
})
},
})
就是这样!现在在你的项目中,你可以在 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/
中的组件时支持热模块替换(HMR)。