Полное руководство по Remix. Часть 1

kgnfk1erl4lfx35dipzjer7psn8.png


Привет, друзья!

В этой серии статей я расскажу вам о Remix — новом фреймворке для создания клиент-серверных веб-приложений на JavaScript (точнее, на React) со встроенной поддержкой TypeScript.

Remix позволяет разрабатывать так называемые PESPA (Progressive Enhancement Single Page Apps — одностраничные приложения с возможностью прогрессивного улучшения). Это означает следующее:


  • почти весь код приложения «живет» на сервере;
  • приложение остается функциональным даже при отсутствии JS;
  • JS используется только для прогрессивного улучшения UX (User Experience — пользовательский опыт).

Подробнее о PESPA и других архитектурах веб-приложений можно почитать здесь.

Очевидно, что разработчики Remix вдохновлялись Next.js и Svelte.

К слову, здесь вы найдете полное руководство по Next.js.

В первой части мы пройдемся по руководствам из официальной документации, во второй — более подробно рассмотрим возможности, предоставляемые Remix, в третьей — разработаем что-нибудь интересное.

Это часть номер раз.


Содержание


Маршрутизация / Routing

Маршрутизация (роутинг) — это, пожалуй, самая важная концепция в Remix. Все начинается с маршрутов (роутов): компилятор, первоначальный запрос документа и почти все последующие действия пользователя.

Начнем с определения понятий:


  • вложенные роуты (nested routes) — связь (map) роутов с сегментами URL обеспечивает соответствие URL определенным компонентам и данным, известным перед рендерингом страницы;
  • URL — полный путь в поисковой строке браузера пользователя. Один URL может совпадать (соответствовать — match) с несколькими роутами. Обратите внимание: роут и URL — разные вещи в Remix;
  • роут (route) или модуль роута (route module) — модуль JS с определенными экспортами (loader, action, default function (компонент) и др.), который соответствует одному или нескольким сегментам URL. Поскольку роут соответствует сегменту URL, по одному пути могут рендерится несколько модулей. Иерархия компонентов соответствует сегментам URL (в основном);
  • путь (path) или путь роута (route path) — сегмент URL, которому соответствует отдельный модуль. Путь роута определяется названием файла в директории app/routes;
  • родительский макет (parent layout route) или родительский роут (parent route) — модуль, который рендерит макет для дочерних компонентов через компонент Outlet;
  • макет без пути (pathless layout route) или роут без пути (pathless route) — модуль, который не добавляет сегменты к URL, но добавляет компонент в иерархию UI (User Interface — пользовательский интерфейс) при совпадении его дочерних роутов;
  • дочерний роут (child route) — модуль, который рендерится внутри родительского Outlet при совпадении его пути с URL;
  • индексный роут (index route) — модуль, который имеет такой же путь, как его родительский роут, но рендерится в качестве дефолтного дочернего роута внутри Outlet;
  • динамический сегмент (dynamic segment) — сегмент пути роута, извлекаемый из URL и передаваемый в приложение, такой как идентификатор (ID) записи или слаг (slug) поста;
  • сплат (splat) — замыкающая звездочка (trailing wildcard) в пути роута, который благодаря этому совпадает со всеми сегментами URL (включая последующий /);
  • аутлет (outlet) — компонент, который рендерится внутри родительского модуля, предназначенный для рендеринга дочерних модулей. Другими словами, аутлет определяет локацию дочерних роутов.


Вложенный роутинг

Вложенный роутинг — это связь между сегментами URL и иерархией компонентов в UI. Сегменты URL определяют:


  • макеты, формирующие страницу;
  • загрузку JS-кода, используемого на странице;
  • загрузку данных, используемых на странице.

Определение роутов

Роуты определяются посредством создания файлов в директории app/routes. Вот как может выглядеть иерархия роутов приложения:

app
├── root.jsx
└── routes
    ├── accounts.jsx
    ├── dashboard.jsx
    ├── expenses.jsx
    ├── index.jsx
    ├── reports.jsx
    ├── sales
    │   ├── customers.jsx
    │   ├── deposits.jsx
    │   ├── index.jsx
    │   ├── invoices
    │   │   ├── $invoiceId.jsx
    │   │   └── index.jsx
    │   ├── invoices.jsx
    │   └── subscriptions.jsx
    └── sales.jsx


  • root.jsx — это корневой роут, служащий макетом для всего приложения. Другие роуты рендерятся внутри его Outlet;
  • обратите внимание на файлы, названия которых совпадают с названиями директорий, в которых эти файлы находятся. Эти файлы предназначены для формирования иерархии макетов компонентов. Например, sales.jsx — это родительский роут для всех дочерних роутов внутри директории app/routes/sales. При совпадении с URL любого роута из этой директории, он будет рендерится внутри Outlet модуля sales.jsx;
  • роут index.jsx будет рендерится внутри Outlet при совпадении URL с путем директории (например, пути example.com/sales соответствует роут app/routes/sales/index.jsx).

Рендеринг иерархии макета роута

Предположим, что URL имеет вид /sales/invoices/123. С этим URL будут совпадать следующие роуты:


  • root.jsx;
  • routes/sales.jsx;
  • routes/sales/invoices.jsx;
  • routes/sales/invoices/$invoiceId.jsx.

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


  
    
      
    
  

Иерархия компонентов полностью соответствует иерархии файлов в директории app/routes:

app
├── root.jsx
└── routes
    ├── sales
    │   ├── invoices
    │   │   └── $invoiceId.jsx
    │   └── invoices.jsx
    ├── sales.jsx
    └── accounts.jsx

Иерархия компонентов для URL /accounts будет такой:


  

Для рендеринга дочерних роутов внутри родительского используется Outlet. root.jsx рендерит основной макет, боковую панель и аутлет для дочерних роутов:

import { Outlet } from "@remix-run/react";

export default function Root() {
  return (
    
      
      {/* ! */}
      
    
  );
}

В свою очередь, sales.jsx рендерит аутлет для всех его дочерних роутов (app/routes/sales/*):

import { Outlet } from "@remix-run/react";

export default function Sales() {
  return (
    

Sales

{/* ! */}
); }

Индексные роуты

Индексный роут — это дефолтный дочерний роут. При отсутствии других дочерних роутов рендерится индексный модуль. Например, URL exmaple.com/sales соответствует индексный роут app/routes/sales/index.jsx.

Индексные роуты не должны рендерить дочерние модули, они являются тупиком для URL. Например, вместо рендеринга глобальной панели навигации в app/routes/index.jsx, ее следует рендерить в app/root.jsx.

Параметр строки запроса ?index

Данный параметр позволяет отличать индексные роуты от их родительский модулей, которые рендерят макеты. Предположим, что у нас имеется такая иерархия роутов:

└── app
    ├── root.jsx
    └── routes
        ├── sales
        │   ├── invoices
        │   │   └── index.jsx
        │   └── invoices.jsx

Какому роуту будет соответствовать путь /sales/invoices? Роуту /sales/invoices.jsx или роуту /sales/invoices/index.jsx? Ответ: роуту /sales/invoices.jsx. Для совпадения с роутом /sales/invoices/index.jsx путь должен заканчиваться параметром строки запроса ?index:

└── app
    ├── root.jsx
    └── routes
        ├── sales
        │   ├── invoices
        │   │   └── index.jsx   <-- /sales/invoices?index
        │   └── invoices.jsx    <-- /sales/invoices

В некоторых случаях добавление ?index происходит автоматически (например, при отправке формы в индексном или его родительском роуте), в других — это делается вручную (например, при использовании fetcher.submit() или fetcher.load()).


Вложенные URL без вложенных макетов

Иногда может потребоваться добавить вложенный URL без добавления компонента в иерархию UI. Рассмотрим страницу редактирования счета:


  • мы хотим, чтобы URL имел вид /sales/invoices/$invoiceId/edit;
  • мы хотим, чтобы соответствующий компонент был прямым потомком корневого компонента.

Другими словами, мы не хотим этого:


  
    
      
        
      
    
  

Мы хотим это:


  

Для создания плоской иерархии UI используется плоское название файла — использование . в названии файла позволяет добавлять сегменты URL без добавления компонентов в иерархию UI:

└── app
    ├── root.jsx
    └── routes
        ├── sales
        │   ├── invoices
        │   │   └── $invoiceId.jsx
        │   └── invoices.jsx
        ├── sales.invoices.$invoiceId.edit.jsx 
    
            

© Habrahabr.ru