Компоненты
Компоненты Astro — это основные строительные блоки любого проекта Astro. Они представляют собой HTML-компоненты с расширением файла .astro, которые не требуют выполнения JavaScript на стороне клиента.
Если вы знаете HTML, вы уже знаете достаточно, чтобы написать свой первый компонент Astro.
Компоненты Astro чрезвычайно гибки. Они могут быть совсем небольшими, например набором общих <meta> тегов для SEO. Компоненты могут быть переиспользуемыми элементами интерфейса, такими как шапка сайта или карточка профиля. Они могут даже содержать макет всей страницы целиком или, находясь в специальной папке src/pages/, быть самой страницей.
Самое важное, что нужно знать о компонентах Astro: они не рендерятся на клиенте. Они превращаются в HTML либо во время сборки, либо по запросу. Вы можете писать JavaScript-код внутри блока метаданных (frontmatter) вашего компонента, и весь этот код будет удален из итоговой страницы, отправляемой пользователю. В результате вы получаете быстрый сайт с нулевым весом JavaScript по умолчанию.
Если вашему компоненту Astro всё же требуется интерактивность в браузере, вы можете добавить стандартные HTML-теги <script> или использовать компоненты UI-фреймворков в качестве «клиентских островков».
Для компонентов, которым требуется отображать персонализированный или динамический контент, вы можете отложить их серверный рендеринг, добавив серверную директиву. Такие «серверные островки» отрендерят своё содержимое, когда оно станет доступно, не задерживая загрузку всей страницы.
Структура компонента
Заголовок раздела «Структура компонента»Компонент Astro состоит из двух основных частей: Скрипта компонента и Шаблона компонента. Каждая часть выполняет свою задачу, но вместе они обеспечивают структуру, которая проста в использовании и достаточно выразительна для любых задач.
---// Скрипт компонента (JavaScript)---<!-- Шаблон компонента (HTML + JS-выражения) -->Скрипт компонента
Заголовок раздела «Скрипт компонента»Astro использует блок кода (---) для выделения скрипта внутри компонента. Если вы когда-либо работали с Markdown, вы наверняка знакомы с концепцией frontmatter (метаданных). Скрипт компонента Astro напрямую вдохновлен этой идеей.
Вы можете использовать скрипт компонента для написания любого JavaScript-кода, необходимого для рендеринга шаблона. Это может быть:
- импорт других компонентов Astro;
- импорт компонентов фреймворков, например React;
- импорт данных (например, JSON-файла);
- получение данных из API или базы данных;
- создание переменных, на которые вы будете ссылаться в шаблоне.
---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 остаётся «за оградой». Он не попадёт в клиентское приложение и не окажется в браузере пользователя. Здесь можно безопасно писать ресурсоёмкий или чувствительный код (например, запросы к приватной базе данных), не беспокоясь о безопасности или производительности клиента.
Скрипт компонента Astro — это TypeScript, что позволяет использовать расширенный синтаксис JavaScript для подсказок в редакторе и проверки ошибок.
Шаблон компонента
Заголовок раздела «Шаблон компонента»Шаблон находится под блоком кода и определяет HTML-структуру вашего компонента.
Если вы напишете здесь обычный HTML, компонент будет отображать этот HTML на любой странице, где он будет использован.
Однако синтаксис шаблонов Astro также поддерживает JavaScript-выражения, теги Astro <style> и <script>, импортированные компоненты и специальные директивы Astro. Данные и значения, определённые в скрипте компонента, можно использовать в шаблоне для динамического создания HTML.
---// Скрипт вашего компонента здесь!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:
---import Button from './Button.astro';---<div> <Button title="Кнопка 1" /> <Button title="Кнопка 2" /> <Button title="Кнопка 3" /></div>Пропсы компонента
Заголовок раздела «Пропсы компонента»Компонент Astro может определять и принимать пропсы (входные данные). Эти пропсы становятся доступны в шаблоне для настройки вывода HTML. В скрипте пропсы доступны через глобальный объект Astro.props.
Вот пример компонента, который принимает пропсы greeting и name. Обратите внимание на деструктуризацию из объекта Astro.props.
---// Использование: <GreetingHeadline greeting="Привет" name="друг" />const { greeting, name } = Astro.props;---<h2>{greeting}, {name}!</h2>При использовании этого компонента в других файлах пропсы передаются как атрибуты:
---import GreetingHeadline from './GreetingHeadline.astro';const name = 'Astro';---<h1>Поздравительная открытка</h1><GreetingHeadline greeting="Привет" name={name} /><p>Желаю вам прекрасного дня!</p>Вы можете типизировать пропсы с помощью интерфейса Props в TypeScript. Astro автоматически подхватит его и обеспечит проверку типов. Пропсам также можно задать значения по умолчанию при деструктуризации.
---interface Props { name: string; greeting?: string;}
const { greeting = "Привет", name } = Astro.props;---<h2>{greeting}, {name}!</h2>Значения по умолчанию будут использованы, если пропсы не переданы явно.
---const { greeting = "Привет", name = "Астронавт" } = Astro.props;---<h2>{greeting}, {name}!</h2>Элемент <slot /> — это заполнитель для внешнего HTML-контента, позволяющий вставлять (или «пробрасывать») дочерние элементы из других файлов в шаблон вашего компонента.
По умолчанию все дочерние элементы, переданные в компонент, будут отрендерены на месте <slot />.
В отличие от пропсов, которые являются атрибутами и доступны в коде через Astro.props, слоты рендерят дочерние HTML-элементы именно там, где они расположены в шаблоне.
---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>---import Wrapper from '../components/Wrapper.astro';---<Wrapper title="Страница Фреда"> <h2>Всё о Фреде</h2> <p>Вот немного информации о Фреде.</p></Wrapper>Этот паттерн лежит в основе компонентов макетов Astro: вы можете «обернуть» всё содержимое страницы тегом макета, чтобы отрендерить его внутри общей структуры.
Astro.slots для расширенных возможностей работы со слотами. Именованные слоты
Заголовок раздела «Именованные слоты»Компонент Astro может иметь несколько именованных слотов. Это позволяет распределять передаваемые HTML-элементы по конкретным местам в шаблоне.
Имя слота задается атрибутом name:
---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 у дочернего элемента.
---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>Используйте атрибут slot="имя-слота" у дочернего элемента, чтобы он попал в соответствующий <slot name="имя-слота" /> вашего компонента.
Чтобы передать несколько элементов в один слот без лишней обёртки <div>, используйте атрибут slot на компоненте Astro <Fragment />:
---// Кастомная таблица с именованными слотами для заголовка и тела---<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>Передача контента:
---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>Обратите внимание, что именованные слоты должны быть прямыми потомками компонента. Нельзя передавать именованные слоты через вложенные элементы.
Именованные слоты также можно передавать в компоненты UI-фреймворков!
Имена слотов Astro нельзя генерировать динамически (например, внутри функции map). Если вам нужна такая функциональность, лучше реализовать её средствами самого UI-фреймворка.
Резервный контент
Заголовок раздела «Резервный контент»Слоты могут содержать резервный контент (fallback). Если в слот ничего не передано, <slot /> отрендерит своё собственное содержимое.
---const { title } = Astro.props;---<div id="content-wrapper"> <h1>{title}</h1> <slot> <p>Это резервный контент, который виден, если в слот ничего не передали.</p> </slot></div>Резервный контент отображается только тогда, когда в именованный слот не передано ни одного элемента с соответствующим атрибутом slot="имя".
Перенос слотов
Заголовок раздела «Перенос слотов»Слоты можно передавать («пробрасывать») другим компонентам. Это полезно при создании вложенных макетов:
------<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>---import BaseLayout from './BaseLayout.astro';---<BaseLayout> <slot name="head" slot="head" /> <slot /></BaseLayout>Именованные слоты можно перебросить в другой компонент, используя одновременно атрибуты name и slot в теге <slot />.
Теперь стандартный слот и слот head, переданные в HomeLayout, будут корректно проброшены в BaseLayout.
HTML-компоненты
Заголовок раздела «HTML-компоненты»Astro поддерживает использование файлов .html в качестве компонентов или страниц (в папке src/pages/). Это удобно для переиспользования кода со старых сайтов или если вы хотите гарантировать отсутствие динамических функций в компоненте.
HTML-компоненты должны содержать только валидный HTML и не поддерживают возможности Astro:
- в них нет блока метаданных (frontmatter), импортов на сервере и динамических выражений;
- любые теги
<script>остаются «как есть» (как если бы у них была директиваis:inline); - они могут ссылаться только на ресурсы из папки
public/.
Элемент <slot /> внутри HTML-компонента будет работать так же, как и в компоненте Astro. Если вы хотите использовать стандартный Web Component Slot, добавьте к нему директиву is:inline.