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
prop。
例如
<script setup lang="ts">
import { SomeComponent } from '#components'
const MyButton = resolveComponent('MyButton')
</script>
<template>
<component :is="clickable ? MyButton : 'div'" />
<component :is="SomeComponent" />
</template>
resolveComponent
来处理动态组件,请确保除了组件的名称外,不要插入任何其他内容,该名称必须是字符串而不是变量。或者,虽然不推荐,但您可以全局注册所有组件,这将为您的所有组件创建异步 chunk,并使它们在整个应用程序中可用。
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 提供了一系列内置的水合策略。每个懒加载组件只能使用一种策略。
v-bind
展开 prop 对象)。它也不适用于从 #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() {
// trigger custom hydration strategy...
isReady.value = true
}
</script>
hydrate-never
永远不水合组件。
<template>
<div>
<LazyMyComponent hydrate-never />
</div>
</template>
监听水合事件
所有延迟水合组件在水合时都会发出 @hydrated
事件。
<template>
<div>
<LazyMyComponent hydrate-on-visible @hydrated="onHydrated" />
</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 { 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
目录。如果您想添加其他目录,或更改如何在此目录的子文件夹中扫描组件,您可以向配置中添加其他目录。
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',
})
},
})
组件扩展名
默认情况下,扩展名在 extensions 键中指定的任何文件都被视为组件。nuxt.config.ts
如果您需要限制应注册为组件的文件扩展名,可以使用组件目录声明的扩展形式及其 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
prop 和 #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 上下文'。
- 您无法从应用程序的其余部分访问 'island 上下文',也无法从 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 文档中阅读更多关于它们的信息。
库作者
制作具有自动 tree-shaking 和组件注册功能的 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)
// Add ./components dir to the list
dirs.push({
path: resolve('./components'),
prefix: 'awesome'
})
}
}
})
就是这样!现在在您的项目中,您可以将您的 UI 库作为 Nuxt 模块导入到您的 nuxt.config
文件中
export default defineNuxtConfig({
modules: ['awesome-ui/nuxt']
})
... 并在我们的 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。