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
后缀,来有选择地将某些组件注册为全局组件。如上所述,每个全局组件都会被渲染为单独的 chunk,所以请谨慎使用此功能。
global
选项也可以针对每个组件目录单独设置。动态导入
若要动态导入组件(也称为延迟加载组件),只需在组件名称前添加 Lazy
前缀即可。这在组件并非始终需要时特别有用。
通过使用 Lazy
前缀,你可以将组件代码的加载延迟到合适的时机,这有助于优化 JavaScript 包的大小。
<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>
延迟(或懒)水合
懒加载组件有助于控制应用中的 chunk 大小,但它们并不总是能提升运行时性能,因为除非条件渲染,它们仍会被主动加载。在真实应用中,一些页面可能包含大量内容和组件,而大多数情况下并非所有组件在页面加载时都需要交互。让它们全部主动加载可能会对性能产生负面影响。
为了优化应用,你可能希望延迟某些组件的水合,直到它们可见,或直到浏览器完成更重要的任务。
Nuxt 支持使用懒(或延迟)水合,让你可以控制组件何时变为可交互状态。
水合策略
Nuxt 提供了一系列内置的水合策略。每个懒组件只能使用一种策略。
hydrate-never
的组件更改 prop 将导致其水合)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
、click
和 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('Component has been hydrated!')
}
</script>
注意事项与最佳实践
延迟水合可以带来性能方面的好处,但正确使用至关重要:
- 优先考虑视口内内容:避免对关键的首屏内容使用延迟水合。该特性更适合那些不需要立即交互的内容。
- 条件渲染:在对懒组件使用
v-if="false"
时,可能不需要延迟水合。你可以直接使用普通的懒组件。 - 共享状态:注意跨多个组件共享状态(如
v-model
)。在一个组件中更新模型可能会触发所有绑定到该模型的组件水合。 - 按策略用途使用:每种策略都是针对特定用途优化的。
hydrate-when
适用于可能并非总是需要水合的组件。hydrate-after
适用于可以等待特定时间的组件。hydrate-on-idle
适用于可以在浏览器空闲时水合的组件。
- 避免对需要交互的组件使用
hydrate-never
:如果组件需要用户交互,则不应设置为永不水合。
直接导入
如果你想或需要绕过 Nuxt 的自动导入功能,你也可以显式地从 #components
导入组件。
<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
目录。如果你想添加其他目录,或更改如何在该目录的子文件夹中扫描组件,可以在配置中添加额外的目录:
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',
})
},
})
<template>
<div>
<!-- the component uses the name we specified and is auto-imported -->
<MyAutoImportedComponent />
</div>
</template>
组件扩展名
默认情况下,任何在 nuxt.config.ts 的 extensions 键 中指定扩展名的文件都会被视为组件。
如果你需要限制应注册为组件的文件扩展名,可以使用组件目录声明的扩展形式及其 extensions
键:
export default defineNuxtConfig({
components: [
{
path: '~/components',
extensions: ['.vue'], },
],
})
客户端组件
如果一个组件只打算在客户端渲染,你可以为组件添加 .client
后缀。
| components/
--| Comments.client.vue
<template>
<div>
<!-- this component will only be rendered on client side -->
<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>
<!--
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
插槽都会传递给它。
在服务端组件中使用客户端组件
experimental.componentIslands.selectiveClient
设置为 true。你可以通过在希望在客户端加载的组件上设置 nuxt-client
属性来实现部分水合。
<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 context”。
- 你无法从应用的其余部分访问该 “island context”,也无法从 island 组件访问应用的其余上下文。换句话说,服务端组件或 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>
<!-- 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 文档中了解更多信息。
库作者
制作具有自动树摇和组件注册的 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 { 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 模块导入:
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。