Последнее время кажется, что React-фреймворки выходят каждый день. Хотя каждому из них есть, что предложить, Remix особенно выделяется. Cам по себе он не новый, но в опенсорс перешел совсем недавно. Изначально он был доступен только по платной подписке.

Remix — React-фреймворк для SSR (server-side rendering). Это означает, что бэкенд и фронтенд реализуются в одном приложении. Рендер происходит на сервере и верстка передается на клиент с минимальным использованием JavaScript. В классическом React-приложении данные сначала отдельно фетчатся, а затем компоненты с ними рендерятся на фронте. Remix, напротив, получает данные на бэкенде и передает отрендеренный HTML напрямую пользователю.

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

Что будет в статье:

Плюсы использования Remix

Remix, как и остальные фреймворки, имеет несколько особенностей «из коробки», которые делают его удобным для разработчиков. Вот некоторые, которые нравятся мне.

Вложенные страницы

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

Еще одно преимущество: для этих вложенных страниц мы можем установить Error Boundary, что поможет с обработкой ошибок в конкретных компонентах.

Error Boundaries

Допустим, вы получили ошибку в Remix-компоненте или вложенном роуте. Ошибки ограничены компонентом, и он либо не отобразится, либо просто покажет ошибку. В других фреймворках это сломает всю страницу, и вы увидите целый экран с ошибкой.

Error Boundaries могут быть реализованы в Next.js, но в Remix они встроены "из коробки". Мне кажется, что это очень удобно для продакшн-сборки, чтобы пользователь не получил блокировку целой страницы из-за простой ошибки.

Переходы

Remix автоматически обрабатывает все состояния загрузки за вас. Все, что нужно сделать — сказать, что нужно показывать во время загрузки приложения. Во фреймворках вроде Next.js вам понадобится установить состояние загрузки с помощью какой-нибудь библиотеки управления состоянием — например Redux или Recoil. То есть на других фреймворках для этого вам нужны дополнительные действия, а в Remix это встроенная возможность.

Традиционные формы

Вернемся в то время, когда разработчики пользовались PHP. Тогда использовался метод отправки формы и action с URLом. В Remix похожий подход.

Я понимаю, что это звучит не очень круто, все привыкли к onClick, onSubmit и HTTP-запросам. Но Remix обрабатывает формы совершенно по-другому. Здесь у вас есть функции вроде action или loader для выполнения  server-side-операций. Данные формы доступны в этих функциях в момент исполнения на сервере, поэтому для ее отправки вообще не нужен JS на фронтенде.

Допустим, у вас есть простой веб-сайт и вам даже не нужен JS на фронте. В этом случае лучше всего сработает классическая отправка формы. В других фреймворках вам придется вызывать fetch или axios, но не в Remix. Это может сильно упростить жизнь.

Недостатки Remix

Хотя Remix имеет много плюсов, некоторые нюансы могут заставить задуматься перед его использованием.

Малое сообщество

Remix лишь недавно заопенсорсили, и пока что в проектах его используют немногие.

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

Непрозрачная система роутинга

Когда я начинал работать с Remix, система маршрутизации меня немного смутила. Я не мог разобраться в концепции вложенных роутов. Пришлось использовать другие фреймворки без этой специфической системы. Поэтому порог входа у Remix чуть выше.

Чем Remix отличается от Next.js?

На первый взгляд, разница небольшая, потому что оба они поддерживают SSR. Однако Next.js поддерживает еще и SSG (Static Site Generation), а Remix фокусируется только на SSR.

Проектируем простое приложение на Remix

Мы рассмотрели некоторые преимущества Remix. Настало время сделать с его помощью простое приложение с погодой. 

Начальные требования:

  • установленный Node.js 

  • Visual Studio Code или другая IDE

  • OpenWeatherMap API ключ — достаточно бесплатного

  • практическое знание React

Если во время работы у вас возникнут трудности, код доступен на GitHub.

Создание Remix-приложения

В терминале введите команду для создания Remix-проекта:

npx create-remix@latest weather-app

weather-app — название проекта, можете заменить его на любое другое. Нажав Enter, вы увидите интерактивное меню, которое поможет создать Remix-приложение:

Здесь вам нужно указать, куда деплоить приложение. В этой статье мы только экспериментируем и не касаемся темы деплоя, поэтому просто используем Remix App Server.

После этого нужно указать, что вы будете использовать: JavaScript или TypeScript. Для простоты руководства я буду использовать JS.

Сейчас нужно указать, следует ли Remix использовать npm install. Нажмите y. Это установит требуемые зависимости.

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

npm install axios dotenv

axios нужен, чтобы можно было отправить HTTP-запрос к API OpenWeatherMap. dotenv мы будем использовать для сохранения API-ключа в переменной окружения.

Теперь давайте отредактируем package.json, чтобы использовать переменные окружения в режиме разработки в Remix. Замените скрипт dev на следующий:

"dev": "node -r dotenv/config node_modules/.bin/remix dev"

Это включит возможность использования переменных окружения в вашем проекте. Теперь создайте новый .env-файл для наших переменных окружения и сохраните API-ключ в следующем формате:

WEATHER_API_KEY={api key here}

Посмотрим на структуру каталога Remix:

Каталог app содержит логику нашего основного приложения. Все файлы и папки в каталоге routes публичны и доступны по URL. Каталог styles содержит все CSS-файлы по аналогии с роутами.

entry.client.jsx и entry.server.jsx управляются через Remix, и эти файлы лучше не трогать. Вместо этого создайте новые файлы и работайте с ними. Файл root.jsx содержит макет нашей общей страницы.

Каталог public содержит статику — изображения, иконки.

Файл remix.config.js содержит базовую конфигурацию нашего приложения Remix, например порт для запуска в режиме разработки.

Очистка

Когда вы впервые настраиваете Remix-приложение, в нем уже есть несколько туториалов и демок. Первым делом удалим их, чтобы можно было поработать над нашим приложением.

Откройте root.jsx и очистите встроенный компонент Layout, чтобы он выглядел так:

function Layout({ children }) {
  return <div>{children}</div>;
}

Перейдите в каталог styles, удалите папку demos и очистите содержимое dark.css и global.css. Это очистит все стили.

Удалите папку demos также в директории routes, потому что нам она не нужна.

Переходите к index.jsx и очистите все. Просто убедитесь, что у вас по умолчанию экспортируется компонент Index:

export default function Index() {
  return <div></div>;
}

Создание формы и получение погоды

Давайте создадим форму в index.jsx со следующей разметкой:

export default function Index() {
  return (
    <div>
      <form action="/weather" method="get">
        City: <input type="text" name="city" />
        <input type="submit" value="Fetch weather" />
      </form>
    </div>
  );
}

Выше мы создали форму с методом get. У инпута есть имя, которое станет query-параметром в URL после отправки формы.

Теперь посмотрим, как пользоваться вложением маршрутов. Создайте файл weather.jsx в папке routes. Он будет обрабатывать урл/weather.

import { Outlet } from "react-router";
export default function Weather() {
  return (
    <>
      <h1>Weather App</h1>
      <Outlet />
    </>
  );
}

Компонент Outlet будет искать папку weather внутри routes и вставлять страницы в основную страницу. Возможно, это поможет вам понять, как работает вложение страниц в Remix.

Теперь создайте папку weather в routes и внутри нее новый файл index.jsx. Давайте напишем функцию loader, которая запустится на стороне сервера, независимо от того, где запросили страницу:

export async function loader({ request }) {
  try {
    const url = new URL(request.url);
    const search = new URLSearchParams(url.search);
    if (!search.get("city")) return redirect("/");
    const city = search.get("city");
    const res = await axios.get(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}&units=metric`
    );
    console.log(res.data);
    return { city, type: res.data.weather[0].main, temp: res.data.main.temp };
  } catch (err) {
    console.error(err);
    redirect("/");
    return {};
  }
}

Выше мы извлекли название города из query-параметров URL. Затем сделали запрос к API OpenWeatherMap, чтобы получить погоду в этом городе. Теперь нам нужно вернуть данные на фронтенд, чтобы они были доступны для рендеринга.

Теперь давайте поработаем над компонентом финального экрана:

export default function Index() {
  const data = useLoaderData();
  return (
    <div>
      <h1>{data.city}</h1>
      <h2>{data.type}</h2>
      <h3>Temperature: {data.temp} °C</h3>
    </div>
  );
}

Хук useLoaderData получает данные, которые были возвращены с использованием функции loader. Если вы все сделали правильно, погода должна отобразиться примерно так:



Поздравляю! Вы сделали первое приложение с использованием Remix!

Заключение

Я думаю, что Remix — мощная технология, которая должна набирать популярность в 2022.

Буду ли я использовать ее вместо Next.js? Может, и нет: у Next.js есть большое сообщество, в отличие от Remix, который только-только перешел в open source.

Это не значит, что мне не нравится этот фреймворк. Я могу использовать его для своих личных проектов. Я хочу еще поэкспериментировать с Error Boundaries. 

Но на мой взгляд, сейчас более предпочтителен Next.js. Чтобы найти в Интернете некоторые проблемы, с которыми я столкнулся в Remix, пришлось потрудиться. Возможно, через несколько лет все изменится, и Remix станет более мощным фреймворком с большой поддержкой сообщества.

А вам интересно попробовать Remix?

Комментарии (4)


  1. ainu
    27.12.2021 14:38

        const res = await axios.get(
          `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}&units=metric`
        );


    А вот у товарищей из Next.js есть библиотека для таких запросов. Которая куда полезней/чаще будет использоваться, чем «отправка формы без JS».

    1-1.

    UPD: я про useSWR (от авторов Next) говорю сейчас, и конечно её можно использовать не только в Next, естественно.


    1. mercifulcarnifex
      27.12.2021 15:46
      +3

      Все-таки "отправка формы без JS" не про это, насколько я понимаю. useSWR это просто хуки с состоянием запроса. А здесь ведь получается, что отправляется форма, сервер Remix выполняет функции action / loader, связанные с компонентом, из которого форма была отправлена, дальше, например, в них получает какие-то данные (на сервере) и возвращает их (или отрисовывает компонент и возвращает верстку, которая синхронизируется с клиентом, это надо разобраться) и попутно еще позволяет получить состояние этого запроса. То есть как бы берет на себя весь цикл выполнения запроса и отрисовки. Не знаю, правильно ли я понял вот эту фишку, на практике не применял конечно еще, но выглядит так.


  1. RokeAlvo
    27.12.2021 15:14
    +1

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

    Как это?


    1. mercifulcarnifex
      27.12.2021 15:19

      Думаю, речь про это: https://remix.run/docs/en/v1/guides/routing#what-it-means

      То есть засчет структуры папок/файлов можно делать некоторые оптимизации.