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"> используйте хелпер resolveComponent из Vue либо импортируйте компонент из #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 откладывает загрузку кода компонента до нужного момента и помогает уменьшить размер JS-бандла.
<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>
Отложенная (ленивая) гидрация
Ленивые компоненты помогают контролировать размер чанков, но не всегда улучшают производительность в рантайме: без условного рендера они всё равно подгружаются сразу. На страницах с большим количеством контента и компонентов далеко не всем нужна интерактивность сразу после загрузки; если всё подгружать сразу, это бьёт по производительности.
Чтобы оптимизировать приложение, имеет смысл отложить гидратацию части компонентов до появления в зоне видимости или пока браузер не освободится для менее критичных задач.
Nuxt поддерживает отложенную (ленивую) гидратацию и позволяет управлять моментом, когда компоненты становятся интерактивными.
Стратегии гидратации
Доступен набор встроенных стратегий гидратации. Для одного ленивого компонента можно выбрать только одну.
hydrate-never приведёт к гидратации).v-bind с объектом пропов). Прямые импорты из #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
Гидратирует компонент, когда окно соответствует 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>
Ограничения и рекомендации
Отложенная гидратация может дать выигрыш в производительности, но важно применять её правильно:
- Приоритет контенту в viewport: не откладывайте гидратацию для критичного контента «над сгибом». Стратегия лучше подходит для того, что не нужно сразу.
- Условный рендер: если на ленивом компоненте стоит
v-if="false", отложенная гидратация часто не нужна — достаточно обычного ленивого компонента. - Общее состояние: учитывайте общее состояние (
v-model) между компонентами. Обновление модели в одном компоненте может запустить гидратацию у всех, привязанных к этой модели. - Назначение стратегий: каждая стратегия заточена под свою задачу.
hydrate-when— когда компонент не всегда должен гидратироваться.hydrate-after— когда можно подождать фиксированное время.hydrate-on-idle— когда гидратацию можно отложить до простоя браузера.
- Не используйте
hydrate-neverдля интерактивных компонентов: если нужен ввод пользователя, компонент не должен оставаться без гидратации.
Прямой импорт
Компоненты можно явно импортировать из #components, если нужно обойти автоимпорт Nuxt.
<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>
Расширения файлов компонентов
По умолчанию компонентом считается любой файл с расширением из ключа 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() обратиться к уже отрендеренному шаблону, в колбэке вызовите await nextTick().Серверные компоненты
Серверные компоненты позволяют рендерить отдельные части интерфейса на сервере внутри клиентского приложения. Их можно использовать в Nuxt даже при статической генерации — так проще смешивать динамику, серверный HTML и статические фрагменты разметки.
Серверный компонент можно использовать отдельно или в паре с клиентским компонентом.
Автономные серверные компоненты
Автономные серверные компоненты всегда рендерятся на сервере (островные компоненты, islands).
При обновлении пропов выполняется сетевой запрос, и 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'; они рендерятся на сервере и на клиенте остаются неинтерактивными.Контекст серверного компонента
При рендере серверного или островного компонента <NuxtIsland> выполняет запрос и получает NuxtIslandResponse (внутренний запрос на сервере или видимый в сети при клиентской навигации).
Это означает:
- На сервере создаётся новое Vue-приложение для формирования
NuxtIslandResponse. - При рендере создаётся новый «островной» контекст.
- Контекст островка недоступен из остального приложения и наоборот — серверный компонент или остров изолирован от остального приложения.
- Плагины при рендере островка выполняются снова, если у них не задано
env: { islands: false }(в синтаксисе объекта плагина).
Внутри островного компонента контекст доступен через nuxtApp.ssrContext.islandContext. Пока островные компоненты экспериментальны, формат контекста может меняться.
<div> с display: contents;В паре с клиентским компонентом
В этом случае пары .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-компонентов с автоматической регистрацией и tree-shaking очень просто. ✨
Используйте метод addComponentsDir из @nuxt/kit, чтобы зарегистрировать каталог компонентов в 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',
})
},
})
Готово: в проекте подключите UI-библиотеку как модуль Nuxt в nuxt.config:
export default defineNuxtConfig({
modules: ['awesome-ui/nuxt'],
})
…и используйте компоненты модуля (с префиксом awesome-) в app/pages/index.vue:
<template>
<div>
My <AwesomeButton>UI button</AwesomeButton>!
<awesome-alert>Here's an alert!</awesome-alert>
</div>
</template>
Компоненты подгружаются только при использовании и поддерживают HMR при правках в node_modules/awesome-ui/components/.