Этот пост подготовлен для блога AG Grid Ареком Наво.
Remix – это новый полнофункциональный JavaScript-фреймворк на основе React Router, который призван объединить быстрый и устойчивый пользовательский опыт (UX) с высококачественным опытом разработки (DX) благодаря прогрессивному улучшению и основам веб-технологий.
В этом руководстве вы узнаете, как использовать Remix с AG Grid, продвинутой высокопроизводительной библиотекой JavaScript Grid, для создания всевозможных приложений с большими таблицами данных и потоковой передачей данных в реальном времени. Благодаря Remix и AG Grid, вы быстро подготовите как фронтенд, так и бэкенд!
Вот как будет работать приложение в двух словах:
Вы можете следить за развитием событий, используя этот репозиторий GitHub.
- Особенности Remix
- Архитектура Remix
- Вложенные маршруты
- Другие возможности
- Интеграция Remix с AG Grid
- Настройка Prisma
- Определение схемы
- Применение схемы
- Создание маршрута ресурса
- Добавление маршрута ресурса Posts
- Добавление тестовых данных
- Подключение к AG Grid
- Определение структуры маршрута
- Подключение источника данных
- Отображение столбцов
- Комплексное тестирование с Cypress
- Развертывание на производстве
- Заключение
Особенности 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, которую легко настроить. Она поставляется с мощными возможностями из коробки, такими как обработка больших данных, потоковая передача данных в реальном времени и построение графиков.