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 来处理动态组件,请确保只插入组件的名称,该名称必须是字面量字符串,不能是变量或包含变量。该字符串在编译阶段进行静态分析。或者,虽然不推荐,你也可以全局注册所有组件,这会为你的所有组件创建异步代码块(async chunks),并使它们在整个应用程序中可用。
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>
延迟(或懒)水合作用
懒加载组件对于控制应用中的代码块大小非常有效,但它们并不总能提升运行时性能,因为除非有条件地渲染,否则它们仍然会预加载。在现实的应用中,某些页面可能包含大量内容和大量组件,而大多数情况下,并非所有组件都需要在页面加载时立即变为可交互状态。让它们全部立即加载可能会对性能产生负面影响。
为了优化你的应用,你可能希望延迟某些组件的水合作用(hydration),直到它们可见,或者直到浏览器完成了更重要的任务。
Nuxt 支持使用延迟(或懒)水合作用,让你能够控制组件何时变得可交互。
水合作用策略
Nuxt 提供了多种内置的水合作用策略。每个懒加载组件只能使用一种策略。
hydrate-never 的组件上的 prop 将导致其进行水合作用)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
在特定交互(例如 click、mouseover)发生后对组件进行水合作用。
<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 () {
// 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="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 上下文'。
- 你不能从应用程序的其余部分访问该 'island 上下文',也不能从该 island 组件中访问应用程序其余部分的上下文。换句话说,服务器组件或 island 与你的应用程序其余部分是隔离的。
- 除非插件设置了
env: { islands: false }(可以在对象语法的插件中完成),否则你的插件在渲染 island 时会再次运行。
在 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 组件库非常简单。✨
你可以在 Nuxt 模块中使用 @nuxt/kit 提供的 addComponentsDir 方法来注册你的组件目录。
想象一下这样的目录结构
-| 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(热模块替换)。