Перейти к содержимому

Компоненты

Компоненты Astro — это основные строительные блоки любого проекта Astro. Они представляют собой HTML-компоненты с расширением файла .astro, которые не требуют выполнения JavaScript на стороне клиента.

Компоненты Astro чрезвычайно гибки. Они могут быть совсем небольшими, например набором общих <meta> тегов для SEO. Компоненты могут быть переиспользуемыми элементами интерфейса, такими как шапка сайта или карточка профиля. Они могут даже содержать макет всей страницы целиком или, находясь в специальной папке src/pages/, быть самой страницей.

Самое важное, что нужно знать о компонентах Astro: они не рендерятся на клиенте. Они превращаются в HTML либо во время сборки, либо по запросу. Вы можете писать JavaScript-код внутри блока метаданных (frontmatter) вашего компонента, и весь этот код будет удален из итоговой страницы, отправляемой пользователю. В результате вы получаете быстрый сайт с нулевым весом JavaScript по умолчанию.

Если вашему компоненту Astro всё же требуется интерактивность в браузере, вы можете добавить стандартные HTML-теги <script> или использовать компоненты UI-фреймворков в качестве «клиентских островков».

Для компонентов, которым требуется отображать персонализированный или динамический контент, вы можете отложить их серверный рендеринг, добавив серверную директиву. Такие «серверные островки» отрендерят своё содержимое, когда оно станет доступно, не задерживая загрузку всей страницы.

Компонент Astro состоит из двух основных частей: Скрипта компонента и Шаблона компонента. Каждая часть выполняет свою задачу, но вместе они обеспечивают структуру, которая проста в использовании и достаточно выразительна для любых задач.

src/components/EmptyComponent.astro
---
// Скрипт компонента (JavaScript)
---
<!-- Шаблон компонента (HTML + JS-выражения) -->

Astro использует блок кода (---) для выделения скрипта внутри компонента. Если вы когда-либо работали с Markdown, вы наверняка знакомы с концепцией frontmatter (метаданных). Скрипт компонента Astro напрямую вдохновлен этой идеей.

Вы можете использовать скрипт компонента для написания любого JavaScript-кода, необходимого для рендеринга шаблона. Это может быть:

  • импорт других компонентов Astro;
  • импорт компонентов фреймворков, например React;
  • импорт данных (например, JSON-файла);
  • получение данных из API или базы данных;
  • создание переменных, на которые вы будете ссылаться в шаблоне.
src/components/MyComponent.astro
---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import SomeReactComponent from '../components/SomeReactComponent.jsx';
import someData from '../data/pokemon.json';
// Доступ к пропсам компонента, например <X title="Привет, мир" />
const { title } = Astro.props;
// Получение данных из внешнего API или базы данных
const data = await fetch('SOME_SECRET_API_URL/users').then(r => r.json());
---
<!-- Ваш шаблон здесь! -->

Блок кода гарантирует, что написанный в нём JavaScript остаётся «за оградой». Он не попадёт в клиентское приложение и не окажется в браузере пользователя. Здесь можно безопасно писать ресурсоёмкий или чувствительный код (например, запросы к приватной базе данных), не беспокоясь о безопасности или производительности клиента.

Шаблон находится под блоком кода и определяет HTML-структуру вашего компонента.

Если вы напишете здесь обычный HTML, компонент будет отображать этот HTML на любой странице, где он будет использован.

Однако синтаксис шаблонов Astro также поддерживает JavaScript-выражения, теги Astro <style> и <script>, импортированные компоненты и специальные директивы Astro. Данные и значения, определённые в скрипте компонента, можно использовать в шаблоне для динамического создания HTML.

src/components/MyFavoritePokemon.astro
---
// Скрипт вашего компонента здесь!
import Banner from '../components/Banner.astro';
import Avatar from '../components/Avatar.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---
<!-- Поддерживаются комментарии HTML! -->
{/* Комментарии JavaScript также валидны! */}
<Banner />
<h1>Привет, мир!</h1>
<!-- Используйте пропсы и переменные из скрипта компонента: -->
<p>{title}</p>
<!-- Отложенный рендеринг компонента с резервным контентом для загрузки: -->
<Avatar server:defer>
<svg slot="fallback" class="generic-avatar" transition:name="avatar">...</svg>
</Avatar>
<!-- Включение компонентов UI-фреймворков с директивой `client:` для гидратации: -->
<ReactPokemonComponent client:visible />
<!-- Комбинирование HTML с JavaScript-выражениями, как в JSX: -->
<ul>
{myFavoritePokemon.map((data) => <li>{data.name}</li>)}
</ul>
<!-- Использование директивы для создания списка классов из строк или объектов: -->
<p class:list={["add", "dynamic", { classNames: true }]} />

Компоненты созданы для того, чтобы быть переиспользуемыми и компонуемыми. Вы можете использовать компоненты внутри других компонентов для создания сложных интерфейсов. Например, компонент Button может быть частью компонента ButtonGroup:

src/components/ButtonGroup.astro
---
import Button from './Button.astro';
---
<div>
<Button title="Кнопка 1" />
<Button title="Кнопка 2" />
<Button title="Кнопка 3" />
</div>

Компонент Astro может определять и принимать пропсы (входные данные). Эти пропсы становятся доступны в шаблоне для настройки вывода HTML. В скрипте пропсы доступны через глобальный объект Astro.props.

Вот пример компонента, который принимает пропсы greeting и name. Обратите внимание на деструктуризацию из объекта Astro.props.

src/components/GreetingHeadline.astro
---
// Использование: <GreetingHeadline greeting="Привет" name="друг" />
const { greeting, name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

При использовании этого компонента в других файлах пропсы передаются как атрибуты:

src/components/GreetingCard.astro
---
import GreetingHeadline from './GreetingHeadline.astro';
const name = 'Astro';
---
<h1>Поздравительная открытка</h1>
<GreetingHeadline greeting="Привет" name={name} />
<p>Желаю вам прекрасного дня!</p>

Вы можете типизировать пропсы с помощью интерфейса Props в TypeScript. Astro автоматически подхватит его и обеспечит проверку типов. Пропсам также можно задать значения по умолчанию при деструктуризации.

src/components/GreetingHeadline.astro
---
interface Props {
name: string;
greeting?: string;
}
const { greeting = "Привет", name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

Значения по умолчанию будут использованы, если пропсы не переданы явно.

src/components/GreetingHeadline.astro
---
const { greeting = "Привет", name = "Астронавт" } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

Элемент <slot /> — это заполнитель для внешнего HTML-контента, позволяющий вставлять (или «пробрасывать») дочерние элементы из других файлов в шаблон вашего компонента.

По умолчанию все дочерние элементы, переданные в компонент, будут отрендерены на месте <slot />.

src/components/Wrapper.astro
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props;
---
<div id="content-wrapper">
<Header />
<Logo />
<h1>{title}</h1>
<slot /> <!-- дочерние элементы попадут сюда -->
<Footer />
</div>
src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Страница Фреда">
<h2>Всё о Фреде</h2>
<p>Вот немного информации о Фреде.</p>
</Wrapper>

Этот паттерн лежит в основе компонентов макетов Astro: вы можете «обернуть» всё содержимое страницы тегом макета, чтобы отрендерить его внутри общей структуры.

См. вспомогательные функции Astro.slots для расширенных возможностей работы со слотами.

Компонент Astro может иметь несколько именованных слотов. Это позволяет распределять передаваемые HTML-элементы по конкретным местам в шаблоне.

Имя слота задается атрибутом name:

src/components/Wrapper.astro
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props;
---
<div id="content-wrapper">
<Header />
<!-- элементы с атрибутом slot="after-header" попадут сюда -->
<slot name="after-header" />
<Logo />
<h1>{title}</h1>
<!-- элементы без атрибута slot или с slot="default" попадут сюда -->
<slot />
<Footer />
<!-- элементы с атрибутом slot="after-footer" попадут сюда -->
<slot name="after-footer" />
</div>

Для вставки контента в конкретный слот используйте атрибут slot у дочернего элемента.

src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Страница Фреда">
<img src="https://my.photo/fred.jpg" slot="after-header" />
<h2>Всё о Фреде</h2>
<p>Вот немного информации о Фреде.</p>
<p slot="after-footer">Copyright 2024</p>
</Wrapper>

Чтобы передать несколько элементов в один слот без лишней обёртки <div>, используйте атрибут slot на компоненте Astro <Fragment />:

src/components/CustomTable.astro
---
// Кастомная таблица с именованными слотами для заголовка и тела
---
<table class="bg-white">
<thead class="sticky top-0 bg-white"><slot name="header" /></thead>
<tbody class="[&_tr:nth-child(odd)]:bg-gray-100"><slot name="body" /></tbody>
</table>

Передача контента:

src/components/StockTable.astro
---
import CustomTable from './CustomTable.astro';
---
<CustomTable>
<Fragment slot="header"> <!-- передаем заголовок таблицы -->
<tr><th>Название товара</th><th>Остаток</th></tr>
</Fragment>
<Fragment slot="body"> <!-- передаем тело таблицы -->
<tr><td>Сланцы</td><td>64</td></tr>
<tr><td>Сапоги</td><td>32</td></tr>
<tr><td>Кроссовки</td><td class="text-red-500">0</td></tr>
</Fragment>
</CustomTable>

Обратите внимание, что именованные слоты должны быть прямыми потомками компонента. Нельзя передавать именованные слоты через вложенные элементы.

Слоты могут содержать резервный контент (fallback). Если в слот ничего не передано, <slot /> отрендерит своё собственное содержимое.

src/components/Wrapper.astro
---
const { title } = Astro.props;
---
<div id="content-wrapper">
<h1>{title}</h1>
<slot>
<p>Это резервный контент, который виден, если в слот ничего не передали.</p>
</slot>
</div>

Резервный контент отображается только тогда, когда в именованный слот не передано ни одного элемента с соответствующим атрибутом slot="имя".

Слоты можно передавать («пробрасывать») другим компонентам. Это полезно при создании вложенных макетов:

src/layouts/BaseLayout.astro
---
---
<html lang="ru">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<slot name="head" />
</head>
<body>
<slot />
</body>
</html>
src/layouts/HomeLayout.astro
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout>
<slot name="head" slot="head" />
<slot />
</BaseLayout>

Теперь стандартный слот и слот head, переданные в HomeLayout, будут корректно проброшены в BaseLayout.

Astro поддерживает использование файлов .html в качестве компонентов или страниц (в папке src/pages/). Это удобно для переиспользования кода со старых сайтов или если вы хотите гарантировать отсутствие динамических функций в компоненте.

HTML-компоненты должны содержать только валидный HTML и не поддерживают возможности Astro:

Узнайте больше об использовании компонентов UI-фреймворков в вашем проекте Astro.