Astro, React и SolidJS танцуют вместе

Присоединяйтесь ко мне в этой недельной заметке, в которой я приглашаю Astro, React и SolidJS на совместные танцы, создавая простое приложение, компоненты которого написаны на разных технологиях, но имеют общее состояние.

В последнее время вокруг Astro много шума, обещаний лучшей производительности и плавной интеграции UI-фреймворков, поэтому я решил взглянуть на него и посмотреть, оправдаются ли эти обещания.
Мне очень нравится идея «островов» Astro, и еще больше меня привлекает тот факт, что вы можете компоновать различные технологии в этих островах и иметь полное взаимодействие между ними.

Итак, чтобы прояснить ситуацию, вот мои цели на этот раз:

  • Запустить приложение Astro
  • Есть компонент отображения счетчика, сделанный с помощью react
  • Компонент контроллера счетчика с кнопками «+» и «-«, сделанный на solidJS.
  • Все они будут находиться на одной странице, разделяя одно и то же состояние, поэтому инкремент или декремент на контроллере счетчика будет влиять на отображение счетчика.
  • Эти два компонента будут находиться на «островах» и будут гидратироваться при загрузке страницы.

Здесь придется потрудиться, так что надевайте ремни и давайте начнем.


Я начинаю с создания нового проекта Astro с помощью инструмента CLI.

yarn create astro

Я выбираю «just the basics» в качестве типа моего проекта (красивая раскраска в терминале, BTW) и предпочитаю не использовать TypeScript в данный момент — бог знает, у нас и так много проблем впереди, не нужно усугублять их 😉
И… все, теперь я могу запустить сайт, используя yarn dev, и сайт действительно появляется в браузере -…

Смотрите, что мы получили

VSCode не очень хорошо обрабатывает .astro файлы, поэтому нам нужно установить плагин для него. Этот плагин, похоже, справляется со своей задачей достаточно хорошо.
У нас есть файл «index.astro», который содержит главную страницу сайта, есть «layout.astro», который является компонентом mainAstro, который фактически содержит HTML документ, имеющий «слот», к которому добавляется содержимое, и есть файл компонента «Card.astro».

В настоящее время меня меньше интересует, как astro компонует свои части. Возможно, мне придется погрузиться в это позже, но теперь, когда я подготовил свой проект, я хотел бы начать интегрировать в него фреймворки, начиная с React.

Интеграция React

Следуя документации, я добавляю интеграцию React в свое приложение:

yarn astro add react

Затем Astro устанавливает необходимые зависимости и вносит некоторые изменения в конфигурационный файл Astro, объявляя, что React теперь интегрирован. Теперь я могу создать свой компонент React CounterDisplay —

import React from 'react';

const CounterDisplay = () => {
   return <div>0</div>;
};

export default CounterDisplay;
Войти в полноэкранный режим Выход из полноэкранного режима

И в моем файле index.astro я буду импортировать мой компонент и использовать его —

---
import Layout from '../layouts/Layout.astro';
import CounterDisplay from '../components/CounterDisplay'
---

<Layout title="Welcome to Astro.">
   <main>
       <CounterDisplay />
   </main>
</Layout>
Войти в полноэкранный режим Выйти из полноэкранного режима

(Я удалил из него все другие OOTB компоненты).

Это работает 🙂 Теперь моя страница выглядит вот так (задержите дыхание) —

Итак, пришло время перейти к другому компоненту SolidJS, который будет отображать две кнопки для увеличения и уменьшения счетчика.

Интеграция SolidJS

Как и в случае с интеграцией React выше, я использую команду astro «add» для создания интеграции:

yarn astro add solid

Теперь, когда интеграция готова, давайте напишем наш компонент CounterController:

import 'solid-js';

const CounterController = () => {
   return (
       <div>
           <button>+</button>
           <button>-</button>
       </div>
   );
};

export default CounterController;
Вход в полноэкранный режим Выход из полноэкранного режима

Вам, наверное, интересно, почему я импортирую solid-js туда, но если не импортировать solid-js, это приведет к ошибке парсинга:

error   Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
Вход в полноэкранный режим Выйти из полноэкранного режима

(Если вы знаете какое-либо другое решение для обхода, пожалуйста, поделитесь в комментариях ниже)

И я добавляю его в файл index.astro, как я это делал для React Component:

---
import Layout from '../layouts/Layout.astro';
import CounterDisplay from '../components/CounterDisplay'
import CounterController from '../components/CounterController'
---

<Layout title="Welcome to Astro.">
   <main>
       <CounterDisplay />
       <CounterController />
   </main>
</Layout>
Войти в полноэкранный режим Выйти из полноэкранного режима

И теперь моя страница выглядит вот так —

Да, это некрасиво, но у нас есть страница, которая содержит 2 компонента, каждый из которых написан на разных технологиях с минимальными усилиями.
Тем не менее, моя страница не делает ничего интересного. Нажатие на кнопки ничего не делает с отображением счетчика. Давайте посмотрим, как мы можем использовать для этого общее состояние.

Совместное использование состояния

Я бы очень хотел использовать для этого сигналы (или store) SolidJS, но поскольку я собираюсь разделить состояние между двумя разными технологиями, рекомендуется использовать нано-хранилища.
Я установлю nanostores и пакеты для React и SolidJS.

yarn add nanostores @nanostores/react @nanostores/solid

Затем я создам файл counter.js в каталоге «stores» и помещу в него это содержимое:

import {atom} from 'nanostores';

export const counter = atom(5);
Войти в полноэкранный режим Выход из полноэкранного режима

(Вы можете прочитать об «атомах» здесь)

Теперь, когда у нас есть установленный магазин, давайте сначала используем его в нашем React-компоненте, который отображает значение счетчика.

import React from 'react';
import {useStore} from '@nanostores/react';
import {counter} from '../stores/counter';

const CounterDisplay = () => {
   const counterValue = useStore(counter);

   return <div>{counterValue}</div>;
};

export default CounterDisplay;
Войти в полноэкранный режим Выйти из полноэкранного режима

Круто — теперь моя страница показывает «5» в качестве значения счетчика.

Пришло время заставить кнопки делать то, что они должны. Я добавляю в компонент CouterController крючок Solid для получения обработчиков событий store и click:

import 'solid-js';
import {useStore} from '@nanostores/solid';
import {counter} from '../stores/counter';

const CounterController = () => {
   const counterValue = useStore(counter);

   return (
       <div>
           <button
               onClick={() => {
                   counter.set(counterValue() + 1);
               }}
           >
               +
           </button>
           <button
               onClick={() => {
                   counter.set(counterValue() - 1);
               }}
           >
               -
           </button>
       </div>
   );
};

export default CounterController;
Вход в полноэкранный режим Выход из полноэкранного режима

Обновляю страницу и… ничего не происходит, когда я нажимаю на кнопку. Почему?

Именно здесь в игру вступает концепция «островов».

Острова Astro

Astro подготавливает статическое содержимое разметки из файлов astro, но как только оно попадает в браузер, не запускается JS, чтобы сделать компоненты страницы интерактивными. Для того чтобы указать ему гидратировать на клиенте, нам нужно добавить директивы «client:…» к нашим компонентам и указать Astro, как мы хотим гидратировать его, а точнее — когда.

Это очень мощная функция, которая позволяет лучше контролировать производительность загрузки страницы и выполнения JS. Я добавлю инструкцию по гидратации моих компонентов при загрузке страницы:

<Layout title="Welcome to Astro.">
   <main>
       <CounterDisplay client:load/>
       <CounterController client:load/>
   </main>
</Layout>
Войти в полноэкранный режим Выйти из полноэкранного режима

И теперь действительно, когда я нажимаю на кнопки, счетчик работает соответствующим образом.

Эй, у нас работает наше приложение!

Проблема с крючками

Я не могу быть настолько хорош, верно?
Вы можете видеть, что оба моих компонента объявлены следующим образом:

const CounterController = () => {
   ...
};

export default CounterController;
Вход в полноэкранный режим Выйти из полноэкранного режима

И этот формат приводит к тому, что Astro выдает эту ошибку:

Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
Enter fullscreen mode Выход из полноэкранного режима

Следуя инструкциям в этой ветке GitHub, для решения этой проблемы вам нужно экспортировать именованную функцию, как показано ниже:

import React from 'react';
import {useStore} from '@nanostores/react';
import {counter} from '../stores/counter';

export default function CounterDisplay() {
   const counterValue = useStore(counter);

   return <div>{counterValue}</div>;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Это, BTW, происходит только для React. Компонент SolidJS не имеет этой проблемы.

Подведение итогов

Итак, что мы имеем —
У нас есть приложение с двумя компонентами, каждый из которых создан по разной технологии, но имеет общее состояние. Мы также можем контролировать, когда мы хотим, чтобы они гидратировались при попадании в браузер.
Включив это, Astro дает нам возможность медленно переходить от одной технологии к другой, или даже иметь несколько UI-фреймворков, сосуществующих на одной странице/приложении. Это очень мощная возможность, в которой давно нуждаются разработчики FE.
Мне очень интересно посмотреть, как она будет развиваться и влиять на другие технологии, которые имеют пересекающиеся функции.

Код, обсуждаемый в этом посте, можно найти в этом репозитории GitHub — https://github.com/mbarzeev/astro-lab.

Как всегда, если у вас есть вопросы или комментарии, обязательно оставьте их в разделе комментариев ниже.

Эй! Если вам понравилось то, что вы только что прочитали, загляните к @mattibarzeev в Twitter 🍻.

Фото Yomex Owo на Unsplash

Оцените статью
devanswers.ru
Добавить комментарий