Использование AG Grid React UI с Remix.run

Этот пост подготовлен для блога AG Grid Ареком Наво.

Remix – это новый полнофункциональный JavaScript-фреймворк на основе React Router, который призван объединить быстрый и устойчивый пользовательский опыт (UX) с высококачественным опытом разработки (DX) благодаря прогрессивному улучшению и основам веб-технологий.

В этом руководстве вы узнаете, как использовать Remix с AG Grid, продвинутой высокопроизводительной библиотекой JavaScript Grid, для создания всевозможных приложений с большими таблицами данных и потоковой передачей данных в реальном времени. Благодаря Remix и AG Grid, вы быстро подготовите как фронтенд, так и бэкенд!

Вот как будет работать приложение в двух словах:

Вы можете следить за развитием событий, используя этот репозиторий GitHub.

Особенности Remix

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

Архитектура Remix

Remix – это полнофункциональный фреймворк, построенный на базе Web Fetch API и ориентированный на рендеринг на стороне сервера (SSR). Он тесно связывает фронтенд и бэкенд, причем каждый маршрут может быть одновременно и пользовательским интерфейсом, и API. Кроме того, благодаря первоклассной поддержке прогрессивного улучшения, Remix может обслуживать любую веб-среду, с JavaScript или без него, постепенно применяя современные функции и оптимизации, сохраняя при этом простоту и соответствие основам веб-технологий.

Вложенные маршруты

Одной из отличительных особенностей Remix являются вложенные маршруты. Благодаря лежащему в основе React Router, Remix может осуществлять предварительную выборку данных и ресурсов для всей страницы или только для ее измененных частей. Это позволяет ускорить время загрузки и улучшить пользовательский опыт, практически не требуя ожидания при взаимодействии с веб-приложением. Кроме того, благодаря выделенным границам ошибок для каждого вложенного маршрута, вы можете быть уверены, что ошибки затрагивают только определенную часть вашего приложения, а не все приложение целиком.

Другие возможности

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

Интеграция Remix с AG Grid

Чтобы начать работу с Remix, убедитесь, что у вас установлен Node.js v14 и npm v7 или новее. Чтобы быстро создать новый проект, используйте Remix CLI.

Запустите проект с помощью следующей команды:

npx create-remix
Войти в полноэкранный режим Выйти из полноэкранного режима

Выполнение этой команды предложит вам ввести директорию и шаблон, который вы хотите использовать. Для этого руководства выберите Just the basics и Remix App Server.

Установив проект, перейдите в его каталог и установите дополнительные зависимости:

npm install ag-grid-react ag-grid-community @prisma/client
npm install -D prisma
Войти в полноэкранный режим Выход из полноэкранного режима

Эти зависимости включают все необходимое для настройки AG Grid и Prisma, современного Node.js ORM (Object-Relational Mapping Tool).

Настройка Prisma

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

Начните с инициализации Prisma:

npx prisma init
Войти в полноэкранный режим Выйти из полноэкранного режима

Приведенная выше команда создаст новый каталог prisma с файлом schema.prisma внутри него, а также .env в корне файла вашего проекта, содержащий строку подключения к базе данных.

Для SQLite укажите путь к месту расположения базы данных в файле .env:

DATABASE_URL="file:./dev.db"
Войти в полноэкранный режим Выйти из полноэкранного режима

Определение схемы

В файле prisma/schema.prisma укажите источник данных, а также все необходимые модели данных:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String
  content   String?
  author    Author   @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Author {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Файлы схем Prisma имеют свой собственный синтаксис и правила, но они вполне читабельны, даже если у вас нет большого опыта работы с Prisma. Приведенная выше схема определяет следующее:

  • Генератор для клиента базы данных, который будет создавать клиентскую библиотеку, специально предназначенную для вашей модели данных.
  • Источник данных, предоставляющий подробную информацию о том, какая база данных должна использоваться, а также все необходимые учетные данные.

Применение схемы

Когда схема готова, вы можете использовать Prisma CLI для генерации миграций и применения их к вашей базе данных с помощью команды prisma migrate:

npx prisma migrate dev --name init
Вход в полноэкранный режим Выйти из полноэкранного режима

Наконец, запустите команду prisma generate, чтобы сгенерировать специальную клиентскую библиотеку, соответствующую вашей схеме:

npx prisma generate
Войти в полноэкранный режим Выйти из полноэкранного режима

Создание маршрута ресурса

Чтобы подключиться к Prisma из вашего приложения Remix, вам придется использовать ранее созданный клиент Prisma. Начните с создания нового файла db.server.js в папке app:

// app/db.server.ts
import { PrismaClient } from "@prisma/client";

let prisma;

if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
} else {
  // Reuse the client when development server restarts
  if (!global.dbClient) {
    global.dbClient = new PrismaClient();
  }
  prisma = global.dbClient;
  prisma.$connect();
}

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

Приведенный выше модуль экспортирует экземпляр PrismaClient. С помощью некоторых дополнительных действий экземпляр кэшируется и повторно используется при перезапуске сервера Remix для оптимизации процесса разработки.

Добавление маршрута ресурса Posts

Чтобы использовать экспортированный экземпляр клиента Prisma, создайте новый маршрут ресурса в app/routes/posts.js:

// app/routes/posts.js
import { prisma } from "../db.server";

export async function loader({ request }) {
  const from = Number(new URL(request.url).searchParams.get("from"));
  const to = Number(new URL(request.url).searchParams.get("to"));

  if (from >= 0 && to > 0) {
    const posts = await prisma.post.findMany({
      skip: from,
      take: to - from,
      select: {
        id: true,
        title: true,
        updatedAt: true,
        author: {
          select: {
            email: true,
            name: true,
          },
        },
      },
    });

    return posts;
  }
  return [];
}
Войдите в полноэкранный режим Выйти из полноэкранного режима

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

В приведенном выше коде загрузчик ресурсов используется с клиентом Prisma. Он возвращает данные в формате JSON, полученные в результате запроса базы данных для получения списка постов.

Пагинация реализована, поскольку маршрут будет использоваться AG Grid для ленивой загрузки данных. Использование параметров URL from и to вместе со смещенной пагинацией Prisma (свойства skip и take) позволяет загружать данные теми частями, которые потребуются гриду.

Используя поле select, вы можете выбрать именно те поля, которые хотите включить в результат запроса, включая поля из отношения, как показано с помощью свойства author.

Добавление тестовых данных

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

Для этого руководства вы можете использовать предварительно заполненный файл базы данных SQLite из репозитория GitHub. В качестве альтернативы вы можете создать специальный сценарий Node.js и использовать его для заполнения базы данных следующим образом:

// test-data.js
import { prisma } from "./app/db.server.js";
import { faker } from "@faker-js/faker";

const generateTestData = async (numberOfPosts) => {
  const author = await prisma.author.create({
    data: {
      email: faker.internet.exampleEmail(),
      name: faker.name.findName(),
    },
  });

  for (let i; i < numberOfPosts; i++) {
    await prisma.post.create({
      data: {
        title: faker.lorem.sentence(getRandomInt(5, 8)),
        content: faker.lorem.paragraph(),
        author: { connect: { id: author.id } },
      },
    });
  }
};

generateTestData(1000);
Войдите в полноэкранный режим Выйти из полноэкранного режима

Сценарий использует библиотеку Faker.js для генерации и заполнения базы данных фальшивыми данными. Также стоит отметить, что метод Prisma createMany не поддерживается SQLite, поэтому посты приходится создавать по отдельности в цикле.

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

node test-data.js
Войти в полноэкранный режим Выйти из полноэкранного режима

Подключение к AG Grid

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

/* app/styles.css */
html {
  height: 100%;
  width: 100%;
}
body {
  margin: 0;
  padding: 1rem;
  height: calc(100% - 2rem);
  width: calc(100% - 2rem);
}
Войти в полноэкранный режим Выход из полноэкранного режима

В Remix файлы CSS загружаются путем экспорта специальной функции links из определенного маршрута. Поскольку этот CSS должен применяться ко всему сайту, вы должны поместить функцию в файл app/root.jsx:

// app/root.jsx
// ...
import styles from "./styles.css";

// ...
export function links() {
  return [{ rel: "stylesheet", href: styles }];
}
Вход в полноэкранный режим Выход из полноэкранного режима

Определение структуры маршрута

Вся логика работы с сеткой и выборкой данных помещается в стандартный файл app/routes/index.jsx.

Начните с определения структуры маршрута:

// app/routes/index.js
import { useCallback, useEffect, useState } from "react";
import { AgGridReact } from "ag-grid-react";
import AgGridStyles from "ag-grid-community/dist/styles/ag-grid.css";
import AgThemeAlpineStyles from "ag-grid-community/dist/styles/ag-theme-alpine.css";
import { useFetcher } from "remix";

// ...
const columnDefs = [
  /* ... */
];
export default function Index() {
  const onGridReady = useCallback((params) => {
    // ...
  }, []);

  return (
    <div className="ag-theme-alpine" style={{ width: "100%", height: "100%" }}>
      <AgGridReact
        columnDefs={columnDefs}
        rowModelType="infinite"
        onGridReady={onGridReady}
      ></AgGridReact>
    </div>
  );
}
export function links() {
  return [
    { rel: "stylesheet", href: AgGridStyles },
    { rel: "stylesheet", href: AgThemeAlpineStyles },
  ];
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете видеть, что стили по умолчанию AG Grid и тема Alpine были загружены через функцию links.

Сама сетка использует модель Infinite Row Model, которая реализует механизм бесконечной прокрутки для ленивой загрузки новых строк по мере прокрутки пользователем. Именно здесь и пригодится ранее созданный маршрут ресурса.

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

Наконец, onGridReady – это обратный вызов, в котором вы можете инициировать подключение источника данных.

Подключение источника данных

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

const [isFetching, setIsFetching] = useState(false);
const [getRowParams, setGetRowParams] = useState(null);
const posts = useFetcher();
// ...
Вход в полноэкранный режим Выход из полноэкранного режима

Затем, внутри обратного вызова onGridReady, создайте и установите datasource:

// ...
const onGridReady = useCallback((params) => {
  const datasource = {
    getRows(params) {
      if (!isFetching) {
        posts.load(`/posts?from=${params.startRow}&to=${params.endRow}`);

        setGetRowParams(params);
        setIsFetching(true);
      }
    },
  };

  params.api.setDatasource(datasource);
}, []);
// ...
Войти в полноэкранный режим Выйти из полноэкранного режима

datasource – это объект конфигурации, который может определять несколько свойств, наиболее важным из которых является getRows, поскольку он отвечает за получение данных.

В приведенном выше коде данные извлекаются из маршрута ресурса /posts только тогда, когда не происходит извлечение других данных. Метод load fetcher’а сначала получает данные, а затем сохраняет их в свойстве data. Таким образом, successCallback, который является частью getRows метода params, сохраняется в getRowParams для последующего использования.

Чтобы установить datasource на сетке, используйте метод api.setDatasource объекта, предоставленного обратному вызову:

useEffect(() => {
  if (getRowParams) {
    const data = posts.data || [];

    getRowParams.successCallback(
      data,
      data.length < getRowParams.endRow - getRowParams.startRow
        ? getRowParams.startRow
        : -1
    );
  }

  setIsFetching(false);
  setGetRowParams(null);
}, [posts.data]);
Вход в полноэкранный режим Выйти из полноэкранного режима

Хук useEffect в приведенном выше коде сработает, когда фетчер загрузит новые данные. Если доступен successCallback, он вызовет его, передавая загруженные данные и последний загруженный ряд (если он известен). После завершения процесса свойства состояния сбрасываются, чтобы быть готовыми к повторной выборке данных.

Отображение столбцов

Когда данные уже загружены, все, что вам нужно, это указать AG Grid, как он должен отображать данные. Это можно сделать с помощью свойства columnDefs компонента AgGridReact:

// ...
const dateFormatter = (params) => {
  if (params.value) {
    return new Date(params.value).toLocaleString();
  }

  return " ";
};
const columnDefs = [
  { field: "id" },
  { field: "title", flex: 1, minWidth: 400 },
  { field: "author.email", minWidth: 250 },
  { field: "author.name" },
  {
    field: "updatedAt",
    valueFormatter: dateFormatter,
  },
];
// ...
Войти в полноэкранный режим Выход из полноэкранного режима

Определения колонок AG Grid (columnDefs) представляют собой массив объектов конфигурации с различными свойствами, наиболее важным из которых является field, поскольку он “связывает” свойство данных с фактической колонкой. Вы можете использовать синтаксис точки (.value) для связи с вложенными свойствами.

Свойства minWidth и flex определяют, как столбец должен масштабироваться по ширине. minWidth определяет минимальную ширину колонки в px, а flex заставляет колонку заполнить все доступное пространство.

Для столбцов с данными, которые требуют дальнейшей обработки или форматирования, можно указать valueFormatter. В приведенном выше примере он используется для преобразования данных ISO в более удобную для пользователя строку локали.

Конечный результат должен выглядеть следующим образом:

Вы можете посмотреть демонстрацию в реальном времени на CodeSandbox.

Комплексное тестирование с Cypress

Хотя приложение уже готово, вы должны протестировать его, чтобы убедиться в отсутствии ошибок для ваших конечных пользователей. Для этого вы можете использовать Cypress, современную систему сквозного тестирования (E2E).

Чтобы начать работу с Cypress, сначала установите необходимые зависимости для разработки:

npm install -D cypress start-server-and-test
Войдите в полноэкранный режим Выйти из полноэкранного режима

Помимо самого Cypress, start-server-and-test – это простая служебная команда, которая позволяет легко запустить сервер разработки и набор E2E-тестирования одной командой.

После установки зависимостей создайте конфигурационный файл cypress.json в корне вашего проекта:

{
  "baseUrl": "http://localhost:3000",
  "integrationFolder": "cypress/e2e"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Конфигурация задает базовый URL для вашего набора тестов, а также расположение интеграционных тестов.

Внутри папки cypress/e2e вы можете разместить все ваши наборы тестов E2E. В качестве примера возьмите следующий тест grid.test.js:

// cypress/e2e/grid.test.js
describe("Grid test", () => {
  it("Should contain rows", () => {
    cy.visit("/");
    const element = cy.get("div.ag-center-cols-container");

    element.children().should("have.length.above", 0);
  });
});
Вход в полноэкранный режим Выход из полноэкранного режима

Этот тест использует Cypress API, чтобы сначала перейти на хост сервера разработки, а затем проверить, содержит ли таблица какие-либо столбцы.

Для запуска тестов используйте команду start-server-and-test:

npx start-server-and-test dev http://localhost:3000 "cypress open"
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда запустит ваш сервер разработки с помощью команды dev, а затем откроет Cypress.

Развертывание на производстве

Благодаря гибкой архитектуре Remix может быть развернут во многих средах – включая бессерверные платформы, контейнеры и серверы Node.js. При этом Remix не создает абстракцию над базовой платформой; он позволяет вам получить доступ ко всем возможностям платформы, но требует некоторых изменений при развертывании на различных объектах.

Приложение в этом руководстве настроено для Remix App Server, который построен поверх Express.

В этом случае, чтобы получить готовое к производству приложение, достаточно создать производственную сборку и запустить сервер Node.js:

npm run build
npm run start
Войти в полноэкранный режим Выйти из полноэкранного режима

Запустив сервер, настройте обратный прокси-сервер, например Nginx, чтобы обеспечить внешний доступ к вашему серверу, и вы готовы к работе!

Заключение

В этом руководстве вы узнали, как объединить Remix и AG Grid для создания быстрого и ориентированного на пользователя приложения для обработки данных. Вы использовали некоторые уникальные возможности Remix и увидели, как он интегрируется с другими инструментами, такими как Prisma ORM или Cypress E2E testing framework.

Полный исходный код этого руководства можно найти здесь.

AG Grid – это высокопроизводительная библиотека таблиц JavaScript, которую легко настроить. Она поставляется с мощными возможностями из коробки, такими как обработка больших данных, потоковая передача данных в реальном времени и построение графиков.

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