Как реализовать простую очередь задач в Node.js

Как реализовать простую очередь задач в Node Изучение

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

Не всегда практично выполнить задачу в момент ее запроса.

Рассмотрим систему управления рассылкой новостей по электронной почте. После написания администратор должен нажать большую красную кнопку «ОТПРАВИТЬ СЕЙЧАС». Приложение могло отправлять каждое электронное письмо немедленно и показывать «завершенный» ответ. Это сработает для дюжины сообщений, но сколько времени потребуется для 1000 подписчиков или больше? Время ожидания запроса браузера истекает до завершения процесса.

Другой пример: пользователь может загрузить любое количество фотографий в приложение галереи. Система изменяет размер и резкость каждого изображения для получения альтернативных размеров. Этот процесс может выполняться при загрузке, но это вызовет задержку для каждого изображения.

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

Очереди

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

  • Любой процесс может отправить (или поставить в очередь) элемент в любое время — например, отправить информационный бюллетень X получателю Y.
  • Любой процесс может получить (или исключить из очереди) элемент в начале очереди — например, элемент, который находился в очереди дольше всех.

Очереди представляют собой структуру FIFO. Первый элемент, добавленный в очередь, будет первым в очереди.

Реализация базовой очереди JavaScript

Вы можете создать очередь, используя массив JavaScript. push()Метод добавляет элемент в конец массива в то время как shift()метод удаляет и возвращает элемент с самого начала:

const queue = [];

 

queue.push( ‘item 1’);

queue.push( ‘item 2’);

 

console.log( queue.shift()); // item 1

console.log( queue.shift()); // item 2

console.log( queue.shift()); // undefined

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

Вы можете использовать класс ES6 для определения любого количества отдельных очередей:

class Queue {

 

constructor() { this.q = []; }

send( item)  { this.q.push( item); }

receive()     { return this.q.shift(); }

 

}

 

// define two queues

const q1 = new Queue();

const q2 = new Queue();

 

q1.send(‘item 1’);

q2.send(‘item 2’);

 

console.log( q1.receive()); // item 1

console.log( q1.receive()); // undefined

console.log( q2.receive()); // item 2

Эти простые примеры могут быть полезны для менее важного клиентского кода, такого как постановка обновлений пользовательского интерфейса в очередь, чтобы обработка происходила в одном обновлении DOM. localStorage или IndexedDB могут при необходимости предлагать уровень сохраняемости данных.

Платформы массового обслуживания

Очереди в памяти менее практичны для сложных серверных приложений:

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

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

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

Программное обеспечение брокера сообщений включает Redis, RabbitMQ, Apache ActiveMQ и Gearman. Облачные службы обмена сообщениями включают Amazon SQS, Azure Service Bus и Google Pub / Sub.

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

Используйте MongoDB в качестве брокера сообщений

С помощью пары сотен строк кода Node.js можно разработать сложную систему очередей.

queue-mongodbМодуль, описанный здесь использует MongoDB для хранения данных, но те же самые концепции могут быть приняты в любой базе данных SQL или NoSQL. Код доступен на GitHub и npm.

Быстрый старт

Убедитесь, что у вас установлен Node.js 14 или более поздней версии, затем создайте новую папку проекта, например queue-test. Добавьте новый package.jsonфайл:

{

«name»: «queue-test»,

«version»: «1.0.0»,

«description»: «Queue test»,

«type»: «module»,

«scripts»: {

«send»: «node./send.js»,

«receive»: «node./receive.js»

}

}

Примечание: «type»: «module»настраивает проект для использования модулей ES6. Они «scripts«будут отправлять и получать элементы в очереди.

Установите модуль queue-mongodb:

npm install @craigbuckler/queue-mongodb

Затем создайте.envфайл с учетными данными для подключения к базе данных MongoDB. Например:

QUEUE_DB_HOST=localhost

QUEUE_DB_PORT=27017

QUEUE_DB_USER=root

QUEUE_DB_PASS=mysecret

QUEUE_DB_NAME=qdb

QUEUE_DB_COLL=queue

Примечание: это создает queueколлекцию ( QUEUE_DB_COLL) в qdbбазе данных ( QUEUE_DB_NAME). Вы можете использовать существующую базу данных, но убедитесь, что коллекция не конфликтует с другой.

Доступ для чтения / записи к базе данных должен быть предоставлен пользователю root( QUEUE_DB_USER) с помощью пароля mysecret( QUEUE_DB_PASS). Если аутентификация не требуется, установите оба значения пустыми.

Запустите базу данных MongoDB, если она еще не запущена. Те, у кого есть Docker и Docker Compose, могут создать новый docker-compose.ymlфайл:

version: ‘3’

 

services:

 

queuedb:

environment:

— MONGO_INITDB_ROOT_USERNAME=${QUEUE_DB_USER}

— MONGO_INITDB_ROOT_PASSWORD=${QUEUE_DB_PASS}

image: mongo:4.4-bionic

container_name: queuedb

volumes:

— queuedata:/data/db

ports:

— «${QUEUE_DB_PORT}:${QUEUE_DB_PORT}»

restart: always

 

volumes:

queuedata:

Затем запустите, docker-compose upчтобы загрузить и запустить MongoDB с постоянным объемом данных.

Docker доступен для Linux, macOS и Windows 10. См. Инструкции по установке Docker.

Создайте новый send.jsфайл, чтобы добавить случайно сгенерированные сообщения электронной почты в очередь с именем news:

// Queue module

import { Queue } from ‘@craigbuckler/queue-mongodb’;

 

// initialize queue named ‘news’

const newsQ = new Queue(‘news’);

 

// random name

const name = String.fromCharCode(65 + Math.random() * 26).repeat(1 + Math.random() * 10);

 

// add object to queue

const send = await newsQ.send({

name:     name,

email:    `${ name.toLowerCase() }@test.com`,

date:     new Date(),

message:  `Hey there, ${ name }!`

});

 

console.log(‘send’, send);

 

// get number of items remaining in queue

console.log(‘items queued:’, await newsQ.count());

 

// close connection and quit

await newsQ.close();

Запустите его с помощью, npm run sendи вы увидите следующий результат:

send {

_id: 607d692563bd6d05bb459931,

sent: 2021-04-19T11:27:33.000Z,

data: {

name: ‘AAA’,

email: ‘aaa@test.com’,

date: 2021-04-19T11:27:33.426Z,

message: ‘Hey there, AAA!’

}

}

items queued: 1

.send()Метод возвращает qItemобъект, содержащий:

  1. документ MongoDB _id
  2. дата / время, когда элемент был первоначально поставлен в очередь, и
  3. копия сообщения data

Запустите сценарий любое количество раз, чтобы добавить дополнительные элементы в очередь. Значение items queuedбудет увеличиваться при каждом запуске.

Теперь создайте новый receive.jsфайл для получения сообщений из той же очереди:

// Queue module

import { Queue } from ‘@craigbuckler/queue-mongodb’;

 

// initialize queue named ‘news’

const newsQ = new Queue(‘news’);

 

let qItem;

 

do {

 

qItem = await newsQ.receive();

 

if (qItem) {

 

console.log(‘\nreceive’, qItem);

 

//… process qItem.data…

//… to send email…

 

}

 

} while (qItem);

 

// number of items remaining in queue

console.log(‘items queued:’, await newsQ.count());

 

await newsQ.close();

Выполнить, npm run receiveчтобы получить и обработать элементы в очереди:

receive {

_id: 607d692563bd6d05bb459931,

sent: 2021-04-19T11:27:33.000Z,

data: {

name: ‘AAA’,

email: ‘aaa@test.com’,

date: 2021-04-19T11:27:33.426Z,

message: ‘Hey there, AAA!’

}

}

items queued: 0

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

Читайте также:  5 вещей, которые я хотел бы знать о Git

Если обработка не удалась — возможно, из-за того, что почтовый сервер не работает — элемент может быть повторно поставлен в очередь следующим образом:

newsQ.send( qItem.data, 600);

Второй 600аргумент — необязательное количество секунд или будущая дата. Эта команда повторно ставит элемент в очередь по истечении 600 секунд (десяти минут).

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

Как queue-mongodb работает модуль

typeСтрока передается в конструктор класса определяет имя очереди..send()Метод создает новый MongoDB документ при передаче данных для добавления в очередь. Документ MongoDB содержит:

  1. MongoDB _id(дата / время создания закодированы внутри значения).
  2. Очередь type.
  3. Именованное значение даты / времени обработки proc. Можно установить время в будущем, но по умолчанию используется текущее время.
  4. Товар data. Это может быть что угодно: логическое значение, число, строка, массив, объект и т.д.

.receive() Метод находит самый старый документ, который имеет соответствия typeи procдаты / времени в прошлом. Документ форматируется, возвращается к вызывающему коду и удаляется из базы данных.

В следующих разделах модуль описывается более подробно.

queue-mongodb Модуль: Инициализация

При необходимости dotenvмодуль считывает.envпеременные среды. Объект подключения к базе данных создается с помощью официального mongodbмодуля драйвера:

// modules

import dotenv from ‘dotenv’;

import mongoDB from ‘mongodb’;

 

// environment variables

if (!process.env.QUEUE_DB_HOST) {

dotenv.config();

}

 

// MongoDB database client

const

dbName = process.env.QUEUE_DB_NAME || ‘qdb’,

qCollectionName = process.env.QUEUE_DB_COLL || ‘queue’,

qAuth = process.env.QUEUE_DB_USER ? `${ process.env.QUEUE_DB_USER }:${ process.env.QUEUE_DB_PASS || » }@`: »,

 

dbClient = new mongoDB.MongoClient(

`mongodb://${ qAuth }${ process.env.QUEUE_DB_HOST || ‘localhost’ }:${ process.env.QUEUE_DB_PORT || ‘27017’ }/`,

{ useNewUrlParser: true, useUnifiedTopology: true }

);

qCollectionПеременный содержит ссылку на коллекцию очереди в базах данных (определяется QUEUE_DB_COLL). Он создается и возвращается dbConnect()функцией, которая также при необходимости определяет схему коллекции и индексы. Все Queueметоды запускаются const q = await dbConnect();для получения ссылки на коллекцию:

let qCollection; // queue collection

 

 

// shared connection

async function dbConnect() {

 

// collection available

if (qCollection) return qCollection;

 

// connect to database

await dbClient.connect();

 

// collection defined?

const

db = dbClient.db( dbName),

colList = await db.listCollections({ name: qCollectionName }, { nameOnly: true }).toArray();

 

if (!colList.length) {

 

// define collection schema

let $jsonSchema = {

bsonType: ‘object’,

required: [ ‘type’, ‘proc’, ‘data’ ],

properties: {

type: { bsonType: ‘string’, minLength: 1 },

proc: { bsonType: ‘date’ }

}

};

await db.createCollection(qCollectionName, { validator: { $jsonSchema } });

 

// define indexes

await db.collection( qCollectionName).createIndexes([

{ key: { type: 1 } },

{ key: { proc: 1 } }

]);

 

}

 

// return queue collection

qCollection = db.collection( qCollectionName);

return qCollection;

 

}

dbClose()Функция закрывает соединение с базой данных:

// close MongoDB database connection

async function dbClose() {

 

if (qCollection) {

await dbClient.close();

qCollection = null;

}

 

}

queue-mongodb Модуль: Queue Конструктор

QueueКонструктор устанавливает очередь typeили имя:

export class Queue {

 

constructor(type = ‘DEFAULT’) {

 

this.type = type;

 

}

queue-mongodb Модуль: Queue.send()Метод

.send()Метод добавляет данные в очередь с подходящим type. У него есть необязательный delayUntilпараметр, который добавляет элемент в очередь в будущем, указав количество секунд или Date().

Метод вставляет новый документ в базу данных и возвращает qItemобъект ({ _id, sent, data}) или в nullслучае неудачи:

async send(data = null, delayUntil) {

 

try {

 

// calculate start date/time

let proc = new Date();

if (delayUntil instanceof Date) {

proc = delayUntil;

}

else if (!isNaN(delayUntil)) {

proc = new Date( +proc + delayUntil * 1000);

}

 

// add item to queue

const

q     = await dbConnect(),

ins   = await q.insertOne({

type: this.type, proc, data

});

 

// return qItem

return ins && ins.insertedCount && ins.insertedId ? { _id: ins.insertedId, sent: ins.insertedId.getTimestamp(), data }: null;

 

}

catch(err) {

 

console.log(`Queue.send error:\n${ err }`);

return null;

 

}

}

queue-mongodb Модуль: Queue.receive()Метод

В.receive()метод извлекает и удаляет самый старый в очередь пункт в базе данных с конкретным typeи procдаты / времени в прошлом. Он возвращает qItemобъект ({ _id, sent, data}) или, nullесли ничего не доступен или возникает ошибка:

async receive() {

 

try {

 

// find and delete next item on queue

const

now = new Date(),

q   = await dbConnect(),

rec = await q.findOneAndDelete(

{

type: this.type,

proc: { $lt: now }

},

{

sort: { proc: 1 }

}

);

 

const v = rec && rec.value;

 

// return qItem

return v ? { _id: v._id, sent: v._id.getTimestamp(), data: v.data }: null;

 

}

catch(err) {

 

console.log(`Queue.receive error:\n${ err }`);

return null;

 

}

 

}

queue-mongodb Модуль: Queue.remove()Метод

.remove()Метод удаляет из очереди элемент идентифицируется qItemобъект ({ _id, sent, data}), возвращаемый.send()методом. Его можно использовать для удаления элемента из очереди независимо от его положения в очереди.

Метод возвращает количество удаленных документов (обычно 1) или nullпри возникновении ошибки:

async remove(qItem) {

 

// no item to remove

if (!qItem || !qItem._id) return null;

 

try {

 

const

q   = await dbConnect(),

del = await q.deleteOne({ _id: qItem._id });

 

return del.deletedCount;

 

}

catch(err) {

 

console.log(`Queue.remove error:\n${ err }`);

return null;

 

}

 

}

queue-mongodb Модуль: Queue.purge()Метод

.purge()Метод удаляет все элементы, поставленные в очередь того же самого typeи возвращает количество удалений:

  async purge() {

 

try {

 

const

q   = await dbConnect(),

del = await q.deleteMany({ type: this.type });

 

return del.deletedCount;

 

}

catch(err) {

 

console.log(`Queue.purge error:\n${ err }`);

return null;

 

}

 

}

queue-mongodb Модуль: Queue.count()Метод

.count()Метод возвращает количество элементов в очереди одного и того же type:

  async count() {

 

try {

 

const q = await dbConnect();

return await q.countDocuments({ type: this.type });

 

}

catch(err) {

 

console.log(`Queue.count error:\n${ err }`);

return null;

 

}

 

}

queue-mongodb Модуль: Queue.close()Метод

.close()Метод запускает dbClose()функцию, чтобы разорвать соединение с базой данных, поэтому цикл событий Node.js может закончиться:

async close() {

 

try {

 

await dbClose();

 

}

catch(err) {

 

console.log(`Queue.close error:\n${ err }`);

return null;

 

}

 

}

 

// end of class

}

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

Однако, поскольку React заботится только об уровне представления приложения, он не применяет какую-либо конкретную архитектуру (например, MVC или MVVM). Это может затруднить организацию вашей кодовой базы по мере роста вашего проекта React.

В 9elements одним из наших флагманских продуктов является PhotoEditorSDK — полностью настраиваемый редактор фотографий, который легко интегрируется в ваше приложение HTML5, iOS или Android. PhotoEditorSDK — масштабное приложение на React, ориентированное на разработчиков. Он требует высокой производительности, небольших размеров и должен быть очень гибким в отношении стиля и особенно тематики.

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

1. Структура каталога

Изначально стиль и код для наших компонентов были разделены. Все стили жили в общем файле CSS (мы используем SCSS для предварительной обработки). Фактический компонент (в данном случае FilterSlider) был отделен от стилей:

├── components

│   └── FilterSlider

│       ├──  __tests__

│       │   └── FilterSlider-test.js

│       └── FilterSlider.jsx

└── styles

└── photo-editor-sdk.scss

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

components

└── FilterSlider

├── __tests__

│   └── FilterSlider-test.js

├── FilterSlider.jsx

└── FilterSlider.scss

Идея заключалась в том, что весь код, принадлежащий компоненту (например, JavaScript, CSS, ресурсы, тесты), находится в одной папке. Это позволяет очень легко извлечь код в модуль npm или, если вы спешите, просто поделиться папкой с другим проектом.

Читайте также:  Методология приложения с двенадцатью факторами

Импорт компонентов

Одним из недостатков этой структуры каталогов является то, что для импорта компонентов требуется импортировать полный путь, например:

import FilterSlider from ’components/FilterSlider/FilterSlider’

Но вот что мы действительно хотели бы написать:

import FilterSlider from ’components/FilterSlider’

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

export { default } from ’./FilterSlider’;

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

components

└── FilterSlider

├── __tests__

│   └── FilterSlider-test.js

├── FilterSlider.jsx

├── FilterSlider.scss

└── package.json

И внутри package.jsonмы используем свойство main, чтобы установить нашу точку входа в компонент, например:

{

«main»: «FilterSlider.jsx»

}

С этим дополнением мы можем импортировать такой компонент:

import FilterSlider from ‘components/FilterSlider’

2. CSS в JavaScript

Стиль и особенно тематика всегда были проблемой. Как упоминалось выше, в нашей первой итерации приложения у нас был большой файл CSS (SCSS), в котором жили все наши классы. Чтобы избежать конфликтов имен, мы использовали глобальный префикс и следовали соглашениям БЭМ при создании имен правил CSS. Когда наше приложение росло, этот подход не очень хорошо масштабировался, поэтому мы искали замену. Сначала мы оценили модули CSS, но тогда у них были проблемы с производительностью. Кроме того, извлечение CSS с помощью плагина Extract Text в веб-паке не работало так хорошо (хотя на момент написания все должно было быть в порядке). Кроме того, этот подход создал сильную зависимость от webpack и значительно усложнил тестирование.

Затем мы оценили некоторые другие решения CSS-in-JS, которые недавно появились на сцене:

  • Стилизованные компоненты: самый популярный выбор в самом большом сообществе
  • EmotionJS: горячий конкурент
  • Linaria: решение с нулевым временем выполнения

Выбор одной из этих библиотек сильно зависит от вашего варианта использования:

  • Вам нужна библиотека для вывода скомпилированного файла CSS для производства? EmotionJS и Linaria могут это сделать! Linaria даже не требует времени выполнения. Он отображает реквизиты в CSS через переменные CSS, что исключает поддержку IE11 — но кому в любом случае нужен IE11?
  • Нужно ли запускать на сервере? Это не проблема для последних версий всех библиотек!

Для структуры каталогов нам нравится помещать все стили в styles.js:

export const Section = styled.section`

padding: 4em;

background: papayawhip;

`;

Таким образом, люди, занимающиеся чистым интерфейсом, также могут редактировать некоторые стили, не имея дела с React, но они должны изучить минимальный JavaScript и как сопоставить реквизиты с атрибутами CSS:

components

└── FilterSlider

├── __tests__

│   └── FilterSlider-test.js

├── styles.js

├── FilterSlider.jsx

└── index.js

Хорошая практика — избавить ваш основной файл компонента от HTML.

Стремление к единой ответственности компонентов React

Когда вы разрабатываете очень абстрактные компоненты пользовательского интерфейса, иногда трудно разделить проблемы. В какой-то момент вашему компоненту потребуется определенная логика предметной области из вашей модели, и тогда все станет беспорядочно. В следующих разделах мы хотели бы показать вам некоторые методы СУШКИ ваших компонентов. Следующие ниже методы частично совпадают по функциональности, и выбор правильного для вашей архитектуры — это скорее предпочтение стиля, а не твердые факты. Но сначала позвольте мне представить варианты использования:

  • Нам пришлось ввести механизм для работы с компонентами, которые зависят от контекста вошедшего в систему пользователя.
  • Нам пришлось визуализировать таблицу с несколькими складными <tbody> элементами.
  • Нам приходилось отображать разные компоненты в зависимости от разных состояний.

В следующем разделе я покажу различные решения описанных выше проблем.

3. Пользовательские крючки

Иногда вам нужно убедиться, что компонент React отображается только тогда, когда пользователь вошел в ваше приложение. Сначала вы будете выполнять некоторые проверки работоспособности во время рендеринга, пока не обнаружите, что часто повторяете себя. Выполняя свою миссию по ОСУШЕНИЮ этого кода, вам рано или поздно придется писать собственные хуки. Не бойтесь: это не так уж и сложно. Взгляните на следующий пример:

import { useEffect } from ‘react’;

import { useAuth } from ‘./use-auth-from-context-or-state-management.js’;

import { useHistory } from ‘react-router-dom’;

 

function useRequireAuth(redirectUrl = «/signup») {

const auth = useAuth();

const history = useHistory();

 

// If auth.user is false that means we’re not

// logged in and should redirect.

useEffect(() => {

if (auth.user === false) {

history.push(redirectUrl);

}

}, [auth, history]);

return auth;

}

useRequireAuthКрючок будет проверить, если пользователь вошел в систему и в противном случае перенаправление на другую страницу. Логика в useAuthхуке может быть предоставлена ​​через контекст или систему управления состоянием, такую ​​как MobX или Redux.

4. Действовать как дети

Создание сворачиваемой строки таблицы — непростая задача. Как вы визуализируете кнопку свертывания? Как мы будем отображать дочерние элементы, когда таблица не свернута? Я знаю, что с JSX 2.0 все стало намного проще, поскольку вы можете возвращать массив вместо одного тега, но я расширю этот пример, поскольку он иллюстрирует хороший вариант использования функции как дочернего шаблона. Представьте себе следующую таблицу:

export default function Table({ children }) {

return (

<table>

<thead>

<tr>

<th>Just a table</th>

</tr>

</thead>

{children}

</table>

);

}

И тело разборного стола:

import { useState } from ‘react’;

 

export default function CollapsibleTableBody({ children }) {

const [collapsed, setCollapsed] = useState(false);

 

const toggleCollapse = () => {

setCollapsed(!collapsed);

};

 

return (

<tbody>

{children(collapsed, toggleCollapse)}

</tbody>

);

}

Вы бы использовали этот компонент следующим образом:

<Table>

<CollapsibleTableBody>

{(collapsed, toggleCollapse) => {

if (collapsed) {

return (

<tr>

<td>

<button onClick={toggleCollapse}>Open</button>

</td>

</tr>

);

} else {

return (

<tr>

<td>

<button onClick={toggleCollapse}>Closed</button>

</td>

<td>CollapsedContent</td>

</tr>

);

}

}}

</CollapsibleTableBody>

</Table>

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

5. Визуализация реквизита

Термин «свойство рендеринга» был придуман Майклом Джексоном, который предположил, что шаблон компонента более высокого порядка можно в 100% случаев заменить обычным компонентом с «опорой рендеринга». Основная идея здесь в том, что все компоненты React являются функциями, а функции могут передаваться как свойства. Так почему бы не передавать компоненты React через props ?! Легкий!

Следующий код пытается обобщить, как получать данные из API. (Обратите внимание, что этот пример предназначен только для демонстрационных целей. В реальных проектах вы бы даже абстрагировали эту логику выборки в useFetchловушку, чтобы еще больше отделить ее от пользовательского интерфейса.) Вот код:

import { useEffect, useState } from «react»;

 

export default function Fetch({ render, url }) {

 

const [state, setState] = useState({

data: {},

isLoading: false

});

 

useEffect(() => {

setState({ data: {}, isLoading: true });

 

const _fetch = async () => {

const res = await fetch(url);

const json = await res.json();

 

setState({

data: json,

isLoading: false,

});

}

 

_fetch();

}, https%3A%2F%2Feditor.sitepoint.com);

 

return render(state);

}

Как видите, есть свойство с именем render, которое является функцией, вызываемой в процессе рендеринга. Функция, вызываемая внутри него, получает в качестве параметра полное состояние и возвращает JSX. Теперь посмотрим на следующее использование:

(

<Fetch

url=»https://api.github.com/users/imgly/repos»

render={({ data, isLoading }) => (

<div>

<h2>img.ly repos</h2>

{isLoading && <h2>Loading…</h2>}

 

<ul>

{data.length > 0 && data.map(repo => (

<li key={repo.id}>

{repo.full_name}

</li>

))}

</ul>

</div>

)} />

Как вы можете видеть, dataи isLoadingпараметры деструктурированные от состояния объекта и могут быть использованы для реакции на JSX. В этом случае, пока обещание не выполнено, отображается заголовок «Загрузка». Вам решать, какие части состояния вы передаете опоре рендеринга и как вы используете их в своем пользовательском интерфейсе. В целом, это очень мощный механизм для извлечения общего поведения пользовательского интерфейса. Функционируют как дети рисунка, описанного выше, в основном по той же схеме, где свойство children.

Совет: поскольку шаблон рендеринга является обобщением функции как дочернего шаблона, ничто не мешает вам иметь несколько реквизитов рендеринга на одном компоненте. Например, Tableкомпонент может получить опору рендеринга для заголовка, а затем еще одну для тела.

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