Вам казали «не блокуйте основний потік» та «розбивайте свої довгі завдання», але що означає робити ці речі?
Якщо ви читаєте багато матеріалів про веб-продуктивності, то поради щодо забезпечення швидкості ваших додатків JavaScript, як правило, включають деякі з цих цікавих фактів:
“Не блокуйте основний потік”.
“Розбивайте свої довгі завдання”.
Що це все означає? Використання меншої кількості JavaScript — це добре, але чи означає це автоматично швидший інтерфейс користувача протягом усього життєвого циклу сторінки? Можливо, а може й ні.
Щоб зрозуміти, чому так важливо оптимізувати завдання JavaScript, вам необхідно зрозуміти роль завдань і те, як браузер їх обробляє — і це починається з розуміння того, що таке завдання.
Що таке завдання?
Завдання це будь-яка окрема частина роботи, яку виконує браузер. Завдання включають такі роботи, як рендеринг, аналіз HTML і CSS, виконання написаного вами коду JavaScript та інші речі, над якими ви не можете безпосередньо контролювати. З усього цього JavaScript, який ви пишете та розгортаєте в Інтернеті, є основним джерелом завдань.
Зображення завдання, click обробником події клацання у профільнику продуктивності в Chrome DevTools.
Завдання впливають продуктивність декількома способами. Наприклад, коли браузер завантажує JavaScript під час запуску, він ставить у чергу завдання для аналізу та компіляції цього JavaScript, щоб його можна було виконати. На пізнішому етапі життєвого циклу сторінки завдання запускаються, коли ваш JavaScript працює, наприклад, управління взаємодією через обробники подій, анімацію на основі JavaScript і фонові дії, такі як збір аналітики. Все це – за винятком веб-воркерів та подібних API – відбувається в основному потоці.
Яка основна нитка?
Основний потік це місце, де в браузері виконується більшість завдань. Він недаремно називається основним потоком: це єдиний потік, в якому майже весь написаний вами JavaScript виконує свою роботу.
Основний потік може обробляти лише одне завдання одночасно. Коли завдання перевищують певну точку – точніше, 50 мілісекунд – вони класифікуються як довгі задачі . Якщо користувач намагається взаємодіяти зі сторінкою під час виконання тривалого завдання або якщо необхідно виконати важливе оновлення рендерингу, браузер затримуватиметься під час виконання цієї роботи. Це призводить до затримки взаємодії чи рендерингу.
Довге завдання, як показано у профільнику продуктивності Chrome. Довгі завдання позначаються червоним трикутником у кутку задачі, а блокуюча частина завдання заповнена візерунком з червоних діагональних смуг.
Вам потрібно розбити завдання. Це означає, що потрібно взяти одну довгу задачу та розділити її на дрібніші завдання, виконання яких окремо потребує менше часу.
Візуалізація одного довгого завдання порівняно з тим самим завданням, розбитим на п’ять більш коротких завдань.
Це важливо, оскільки коли завдання розбиті на частини, у браузера з’являється більше можливостей реагувати на пріоритетнішу роботу, включаючи взаємодію з користувачем.
Візуалізація того, що відбувається з взаємодіями, коли завдання занадто довгі і браузер не може реагувати на взаємодії досить швидко, а також коли більш довгі завдання розбиваються більш дрібні завдання.
У верхній частині попереднього малюнка обробник подій, поставлений у чергу в результаті взаємодії з користувачем, повинен був дочекатися одного довгого завдання, перш ніж він зможе запуститися. Це затримує взаємодію. Внизу обробник подій може запуститися раніше. Оскільки обробник подій мав можливість запускатись між меншими завданнями, він запускається раніше, ніж якби йому доводилося чекати завершення довгого завдання. У верхньому прикладі користувач міг помітити затримку; внизу взаємодія могла здатися миттєвою .
Проблема, однак, у тому, що поради «розбивати свої довгі завдання» та «не блокувати основний потік» є недостатньо конкретними, якщо ви вже не знаєте, як це робити. Це те, що пояснить це керівництво.
Стратегії управління завданнями
Поширена порада в архітектурі програмного забезпечення – розбити вашу роботу на дрібніші функції. Це дає вам переваги кращої читання коду та зручності супроводу проекту. Це також полегшує написання тестів.
function saveSettings () { validateForm(); showSpinner(); saveToDatabase(); updateUI(); sendAnalytics();}
У цьому прикладі є функція з ім’ям saveSettings() , яка викликає п’ять функцій у собі виконання певної роботи, наприклад перевірки форми, відображення лічильника, відправки даних тощо. буд. Концептуально це добре спроектовано. Якщо вам потрібно налагодити одну з цих функцій, можна переглянути дерево проекту, щоб з’ясувати, що робить кожна функція.
Проблема, однак, полягає в тому, що JavaScript не запускає кожну з цих функцій як окремі завдання, оскільки вони виконуються всередині функції saveSettings() . Це означає, що всі п’ять функцій виконуються як одне завдання.
Одна функція saveSettings() , що викликає п’ять функцій. Робота виконується як частина одного довгого монолітного завдання.
У кращому випадку, навіть одна з цих функцій може збільшити загальну тривалість завдання на 50 або більше мілісекунд. У найгіршому випадку більшість із цих завдань можуть виконуватися трохи довше, особливо на пристроях з обмеженими ресурсами. Далі йде набір стратегій, які ви можете використовувати для поділу завдань та визначення їхньої пріоритетності.
Вручну відкласти виконання коду
Один із методів, який розробники використовували для розбиття завдань на дрібніші, — це setTimeout() . Використовуючи цей метод, ви передаєте функцію setTimeout() . Це переносить зворотний дзвінок в окреме завдання, навіть якщо ви вкажете тайм 0 .
function saveSettings () { // Do critical work that is user-visible: validateForm(); showSpinner(); updateUI();// Defer work that isn't user-visible to a separate task: setTimeout(() => { saveToDatabase(); sendAnalytics(); }, 0);}
Це добре працює, якщо у вас є ряд функцій, які потрібно виконувати послідовно, але ваш код не завжди може бути організований таким чином. Наприклад, у вас може бути великий обсяг даних, які потрібно обробити в циклі, і це завдання може зайняти дуже багато часу, якщо у вас мільйони елементів.
function processData () { for (const item of largeDataArray) { // Process the individual item here. }}
Використання setTimeout() тут проблематичне, оскільки його ергономіка ускладнює реалізацію, а обробка всього масиву даних може зайняти багато часу, навіть якщо кожен елемент можна обробити дуже швидко. Все це складається, і setTimeout() не є відповідним інструментом для цієї роботи, принаймні при такому використанні.
Крім setTimeout() , існує кілька інших API, які дозволяють відкласти виконання коду до наступного завдання. Один із них передбачає використання postMessage() для більш швидкого тайм-ауту. Ви також можете розбити роботу за допомогою requestIdleCallback() , але будьте обережні! — requestIdleCallback() планує завдання із мінімально можливим пріоритетом і лише під час простою браузера. Коли основний потік перевантажений, завдання, заплановані за допомогою requestIdleCallback() , можуть не запуститися.
Використовуйте async/await для створення точок плинності
У частині цього керівництва, що залишилася, ви зустрінете фразу «поступитися основною ниткою», але що це означає? Чому вам слід зробити це? Коли вам це потрібно зробити?
Коли завдання розбиті на частини, іншим завданням краще розставити пріоритети за допомогою внутрішньої схеми пріоритезації браузера. Один із способів переходу до основного потоку включає використання комбінації Promise , яка дозволяється викликом setTimeout() :
function yieldToMain () { return new Promise(resolve => { setTimeout(resolve, 0); });}
У функції saveSettings() можна перейти до основного потоку після кожної частини роботи, якщо ви await функцію yieldToMain() після кожного виклику функції:
async function saveSettings () { // Create an array of functions to run: const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ]// Loop over the tasks: while (tasks.length > 0) { // Shift the first task off the tasks array: const task = tasks.shift();// Run the task: task();// Yield to the main thread: await yieldToMain(); }}
В результаті колись монолітне завдання тепер розбито на окремі завдання.
Функція saveSettings() тепер виконує свої дочірні функції окремі завдання.
Перевага використання підходу, заснованого на обіцянках, замість ручного використання setTimeout() полягає у кращій ергономіці. Точки прибутковості стають декларативними, і тому їх легше писати, читати та розуміти.
Поступайтеся тільки за необхідності
Що робити, якщо у вас є безліч завдань, але ви хочете виконати їх тільки в тому випадку, якщо користувач спробує взаємодіяти зі сторінкою? Саме для цього і був створений метод isInputPending() .
isInputPending() — це функція, яку можна запустити в будь-який час, щоб визначити, чи намагається користувач взаємодіяти з елементом сторінки: виклик isInputPending() поверне true . Інакше він повертає false .
Припустимо, у вас є черга завдань, які вам потрібно виконати, але ви не хочете заважати жодним вхідним даним. Цей код, який використовує як isInputPending() так і нашу спеціальну функцію yieldToMain() , гарантує, що введення не буде затримано, поки користувач намагається взаємодіяти зі сторінкою:
async function saveSettings () { // A task queue of functions const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ];while (tasks.length > 0) { // Yield to a pending user input: if (navigator.scheduling.isInputPending()) { // There's a pending user input. Yield here: await yieldToMain(); } else { // Shift the task out of the queue: const task = tasks.shift();// Run the task: task(); } }}
Під час роботи saveSettings() він циклічно перебиратиме завдання у черзі. Якщо isInputPending() повертає true під час циклу, saveSettings() викличе yieldToMain() , щоб можна було обробити введення користувача. В іншому випадку наступне завдання буде перенесено з початку черги і виконуватиметься безперервно. Він робитиме це доти, доки не залишиться більше завдань.
saveSettings() запускає чергу завдань для п’яти завдань, але користувач клацнув, щоб відкрити меню, доки виконувався другий робочий елемент. isInputPending() передає основному потоку обробку взаємодії та відновлює виконання інших завдань.
Використання isInputPending() у поєднанні з механізмом передачі — відмінний спосіб змусити браузер зупинити будь-які завдання, які він обробляє, щоб він міг реагувати на критичні взаємодії з користувачем. Це може допомогти покращити здатність вашої сторінки реагувати на запити користувача у багатьох ситуаціях, коли виконується безліч завдань.
Інший спосіб використання isInputPending() – особливо якщо ви турбуєтеся про надання запасного варіанту для браузерів, які його не підтримують, – це використовувати підхід, заснований на часі, у поєднанні з необов’язковим оператором ланцюжка :
async function saveSettings () { // A task queue of functions const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ]; let deadline = performance.now() + 50;while (tasks.length > 0) { // Optional chaining operator used here helps to avoid // errors in browsers that don't support `isInputPending`: if (navigator.scheduling?.isInputPending() || performance.now() >= deadline) { // There's a pending user input, or the // deadline has been reached. Yield here: await yieldToMain();// Extend the deadline: deadline = performance.now() + 50;// Stop the execution of the current loop and // move onto the next iteration: continue; }// Shift the task out of the queue: const task = tasks.shift();// Run the task: task(); }}
При такому підході ви отримуєте запасний варіант для браузерів, які не підтримують isInputPending() , використовуючи підхід, заснований на часі, який використовує (і коригує) крайній термін, так що робота буде розбита там, де це необхідно, чи то шляхом поступки введення користувача або або до певний момент часу.
Прогалини в поточних API
Згадані досі API-інтерфейси можуть допомогти вам розбити завдання, але у них є суттєвий недолік: коли ви поступаєтеся основному потоку, відкладаючи виконання коду в наступному завданні, цей код додається в кінець черги завдань.
Якщо ви контролюєте весь код на своїй сторінці, можна створити власний планувальник з можливістю пріоритезації завдань, але сторонні скрипти не будуть використовувати ваш планувальник. По суті, за таких умов ви не зможете розставити пріоритети у роботі. Ви можете розбити його на частини або явно підкоритися взаємодіям з користувачем.
На щастя, в даний час знаходиться у розробці спеціальний API-інтерфейс планувальника, який вирішує ці проблеми.
Спеціальний API планувальника
API-інтерфейс планувальника зараз пропонує функцію postTask() , яка на момент написання доступна в браузерах Chromium і Firefox під прапором. postTask() забезпечує більш детальне планування завдань і є одним із способів допомогти браузеру розставити пріоритети в роботі, щоб завдання з низьким пріоритетом поступалися місцем основного потоку. postTask() використовує обіцянки та приймає налаштування priority .
API postTask() має три пріоритети, які можна використовувати:
'background' для завдань із найнижчим пріоритетом.
'user-visible' для завдань із середнім пріоритетом. Це значення за промовчанням, якщо priority не встановлено.
'user-blocking' для критичних завдань, які необхідно виконувати із високим пріоритетом.
Як приклад візьмемо наступний код, де API postTask() використовується для запуску трьох завдань з максимально можливим пріоритетом, а двох завдань, що залишилися, — з мінімально можливим пріоритетом.
function saveSettings () { // Validate the form at high priority scheduler.postTask(validateForm, {priority: 'user-blocking'});// Show the spinner at high priority: scheduler.postTask(showSpinner, {priority: 'user-blocking'});// Update the database in the background: scheduler.postTask(saveToDatabase, {priority: 'background'});// Update the user interface at high priority: scheduler.postTask(updateUI, {priority: 'user-blocking'});// Send analytics data in the background: scheduler.postTask(sendAnalytics, {priority: 'background'});};
Тут пріоритет завдань планується таким чином, щоб завдання з пріоритетом браузера, такі як взаємодія з користувачем могли виконуватися.
Під час запуску saveSettings() функція планує виконання окремих функцій за допомогою postTask() . Критична робота, пов’язана з користувачем, запланована з високим пріоритетом, а робота, про яку користувач не знає, запланована для виконання у фоновому режимі. Це дозволяє швидше виконувати взаємодію з користувачем, оскільки робота розбивається на частини та відповідним чином розподіляється за пріоритетами.
Це спрощений приклад використання postTask() . Можна створювати екземпляри різних об’єктів TaskController , які можуть розділяти пріоритети між завданнями, включаючи можливість змінювати пріоритети для різних екземплярів TaskController у міру потреби.
Вбудований вихід із продовженням через scheduler.yield
async function saveSettings () { // Create an array of functions to run: const tasks = [ validateForm, showSpinner, saveToDatabase, updateUI, sendAnalytics ]// Loop over the tasks: while (tasks.length > 0) { // Shift the first task off the tasks array: const task = tasks.shift();// Run the task: task();// Yield to the main thread with the scheduler // API's own yielding mechanism: await scheduler.yield(); }}
Ви помітите, що наведений вище код багато в чому вам знайомий, але замість використання yieldToMain() ви викликаєте і await scheduler.yield() .
Візуалізація виконання завдання без поступки, зі поступкою, а також зі поступкою та продовженням. При використанні scheduler.yield() виконання завдання поновлюється з того місця, де його було зупинено, навіть після точки виходу.
Перевага scheduler.yield() полягає у продовженні. Це означає, що якщо ви поступитеся серединою набору завдань, інші заплановані завдання продовжаться в тому ж порядку після точки виходу. Це не дозволяє коду сторонніх скриптів узурпувати порядок виконання коду.
Висновок
Керувати завданнями складно, але це допомагає вашій сторінці швидше реагувати на взаємодію користувача. Не існує єдиної ради з управління завданнями та розміщення пріоритетів. Скоріше це кілька різних технік. Ще раз повторю: ось основні моменти, які слід враховувати під час управління завданнями:
Перейдіть до основного потоку для вирішення критичних завдань, з якими стикається користувач.
Використовуйте isInputPending() , щоб перейти до основного потоку, коли користувач намагається взаємодіяти зі сторінкою.
Розставте пріоритети задач за допомогою postTask() .
Нарешті, виконуйте якнайменше роботи у своїх функціях.
За допомогою одного або кількох цих інструментів ви зможете структурувати роботу своєї програми так, щоб вона віддавала пріоритет потребам користувача, гарантуючи при цьому виконання менш важливої роботи. Це покращить досвід користувача, зробить його більш чуйним і приємним у використанні.
Створення веб-додатків з використанням мікрофронтендів та Module Federation
У цій статті ми розберемо процес розробки веб-застосунку на основі підходу мікрофронтендів з використанням технології Module Federation.
Мікрофронтенди – це підхід у веб-розробці, при якому фронтенд поділяється на безліч дрібних, автономних елементів. Ці частини розробляються різними командами, мабуть, з використанням різних технологій, але в результаті вони спільно функціонують як єдине ціле. Такий підхід дозволяє вирішувати проблеми, пов’язані з великими програмами, спрощує процес розробки та тестування, сприяє використанню різноманітних технологій та покращує можливості повторного використання коду.
Мета нашого проекту – створити банківську програму, що має функціональність для перегляду та редагування банківських карток і транзакцій.
На схемі представлена архітектура веб-додатку, що використовує мікрофронтенд з інтеграцією через Module Federation. Вгорі зображення знаходиться Host , який є головним додатком (Main app) і служить контейнером для інших додатків.
Існують два мікрофронтенди: Cards і Transactions , кожне з яких розроблено окремою командою і виконує свої функції в рамках банківського додатку.
Також на схемі є компонент Shared , який містить загальні ресурси, такі як типи даних, утиліти, компоненти та інше. Ці ресурси імпортуються як у Host, так і в мікрододатки Cards та Transactions, що забезпечує консистентність та перевикористання коду у всій екосистемі програми.
Крім того, тут зображено Event Bus , який є механізмом для обміну повідомленнями та подіями між компонентами системи. Це забезпечує спілкування між Host і мікропрограмами, а також між самими мікропрограмами, що дозволяє їм реагувати на зміни станів.
Дана схема демонструє модульну та розширювану структуру веб-додатку, що є однією з ключових переваг підходу мікрофронтендів. Це дозволяє розробляти програми, які легше підтримувати, оновлювати та масштабувати.
Ми організуємо наші програми всередині директорії packages та налаштуємо Yarn Workspaces, що дозволить нам ефективно використовувати спільні компоненти з модуля shared між різними пакетами.
"workspaces": [
"packages/*"
],
Module Federation, введений Webpack 5, дозволяє різним частинам програми завантажувати код один одного динамічно. За допомогою цієї функції ми забезпечимо асинхронне завантаження компонентів
Webpack-конфіг для host-програми
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
// Решта конфігурації Webpack, не пов'язана безпосередньо з Module Federation// ...plugins: [
// Плагін Module Federation для інтеграції мікрофронтендівnew ModuleFederationPlugin({
remotes: {
// Визначення віддалених мікрофронтендів, доступних для цього мікрофронтенду'remote-modules-transactions': isProduction
? 'remoteModulesTransactions@https://microfrontend.fancy-app.site/apps/transactions/remoteEntry.js'
: 'remoteModulesTransactions@http://localhost:3003/remoteEntry.js',
'remote-modules-cards': isProduction
? 'remoteModulesCards@https://microfrontend.fancy-app.site/apps/cards/remoteEntry.js'
: 'remoteModulesCards@http://localhost:3001/remoteEntry.js',
},
shared: {
// Визначення загальних залежностей між різними мікрофронтендамиreact: { singleton: true, requiredVersion: deps.react },
antd: { singleton: true, requiredVersion: deps['antd'] },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
'react-redux': { singleton: true, requiredVersion: deps['react-redux'] },
axios: { singleton: true, requiredVersion: deps['axios'] },
},
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html'), // Шаблон HTML для Webpack
}),
],
// Інші налаштування Webpack// ...
};
Webpack-конфіг для програми “Банківські карти”
const path = require('path');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const deps = require('./package.json').dependencies;
module.exports = {
// Інші налаштування Webpack...plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html'), // Шаблон HTML для Webpack
}),
// Налаштування Module Federation Pluginnew ModuleFederationPlugin({
name: 'remoteModulesCards', // Ім'я мікрофронтендуfilename: 'remoteEntry.js', // Ім'я файлу, який буде точкою входу для мікрофронтендуexposes: {
'./Cards': './src/root', // Визначає, які модулі та компоненти будуть доступні для інших мікрофронтендів.
},
shared: {
// Визначення залежностей, які будуть використовуватися як спільні між різними мікрофронтендамиreact: { requiredVersion: deps.react, singleton: true },
antd: { singleton: true, requiredVersion: deps['antd'] },
'react-dom': { requiredVersion: deps['react-dom'], singleton: true },
'react-redux': { singleton: true, requiredVersion: deps['react-redux'] },
axios: { singleton: true, requiredVersion: deps['axios'] },
},
}),
],
// Інші налаштування Webpack...
};
Тепер ми легко можемо імпортувати наші програми в host-додаток.
import React, { Suspense, useEffect } from'react';
import { BrowserRouter as Router, Route, Routes } from'react-router-dom';
import { Main } from'../pages/Main';
import { MainLayout } from'@host/layouts/MainLayout';
// Лініве завантаження компонентів Cards та Transactions з віддалених модулівconst Cards = React.lazy(() =>import('remote-modules-cards/Cards'));
const Transactions = React.lazy(() =>import('remote-modules-transactions/Transactions'));
const Pages = () => {
return (
<Router><MainLayout>
{/* Використання Suspense для керування станом завантаження асинхронних компонентів */}
<Suspensefallback={<div>Loading...</div>}>
<Routes><Routepath={'/'} element={<Main />} />
<Routepath={'/cards/*'} element={<Cards />} />
<Routepath={'/transactions/*'} element={<Transactions />} />
</Routes></Suspense></MainLayout></Router>
);
};
exportdefault Pages;
Далі для команди “Банківські карти” налаштуємо Redux Toolkit
// Імпортуємо функцію configureStore із бібліотеки Redux Toolkitimport { configureStore } from'@reduxjs/toolkit';
// Імпортуємо кореневий редьюсерimport rootReducer from'./features';
// Створюємо сховище за допомогою функції configureStoreconst store = configureStore({
// Встановлюємо кореневий редьюсерreducer: rootReducer,
// Встановлюємо проміжне програмне забезпечення за замовчуваннямmiddleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});
// Експортуємо сховищеexportdefault store;
// Визначаємо типи для диспетчера та стану програмиexport type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
// Імпортуємо Reactimport React from'react';
// Імпортуємо головний компонент програмиimport App from'../app/App';
// Імпортуємо Provider із react-redux для зв'язку React та Reduximport { Provider } from'react-redux';
// Імпортуємо наше сховище Reduximport store from'@modules/cards/store/store';
// Створюємо головний компонент Indexconst Index = (): JSX.Element => {
return (
// Повертаємо наш додаток у Provider, передаючи в нього наше сховище<Providerstore={store}><App /></Provider>
);
};
// Експортуємо головний компонентexportdefault Index;
У додатку має бути система ролей:
USER – може переглядати сторінки,
MANAGER – має право на редагування,
ADMIN – може редагувати та видаляти дані.
Host-програма надсилає запит на сервер для отримання інформації про користувача та зберігає ці дані у своєму сховищі. Необхідно ізольовано отримати ці дані у додатку “Банківські карти”.
Для цього потрібно написати middleware для Redux-сторонка host-додатку, щоб зберігати дані в глобальний об’єкт window
// Імпортуємо функцію configureStore та тип Middleware з бібліотеки Redux Toolkitimport { configureStore, Middleware } from'@reduxjs/toolkit';
// Імпортуємо кореневий редьюсер та тип RootStateimport rootReducer, { RootState } from'./features';
// Створюємо проміжне програмне забезпечення, яке зберігає стан програми в глобальному об'єкті windowconst windowStateMiddleware: Middleware<{}, RootState> =
(store) =>(next) =>(action) => {
const result = next(action);
(windowas any).host = store.getState();
return result;
};
// Функція завантаження стану з глобального об'єкта windowconst loadFromWindow = (): RootState | undefined => {
try {
const hostState = (windowas any).host;
if (hostState === null) returnundefined;
return hostState;
} catch (e) {
console.warn('Error loading state from window:', e);
returnundefined;
}
};
// Створюємо сховище за допомогою функції configureStoreconst store = configureStore({
// Встановлюємо кореневий редьюсерreducer: rootReducer,
// Додаємо проміжне ПЗ, яке зберігає стан у windowmiddleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(windowStateMiddleware),
// Завантажуємо попередній стан із windowpreloadedState: loadFromWindow(),
});
// Експортуємо сховищеexportdefault store;
// Визначаємо тип для диспетчераexport type AppDispatch = typeof store.dispatch;
Для синхронізації зміни ролі користувача між усіма мікрофронтендами ми використовуємо event bus . У модулі shared реалізуємо обробники для відправки та прийому подій.
// Імпортуємо канали подій та типи ролейimport { Channels } from'@/events/const/channels';
import { EnumRole } from'@/types';
// Оголошуємо змінну для обробника подійlet eventHandler: ((event: Event) =>void) | null = null;
// Функція обробки зміни ролі користувачаexportconst onChangeUserRole = (cb: (role: EnumRole) =>void): void => {
// Створюємо обробник подій
eventHandler = (event: Event) => {
// Наводимо подію до типу CustomEventconst customEvent = event as CustomEvent<{ role: EnumRole }>;
// Якщо у події є деталі, виводимо їх у консоль і викликаємо callback-функціюif (customEvent.detail) {
console.log(`On ${Channels.changeUserRole} - ${customEvent.detail.role}`);
cb(customEvent.detail.role);
}
};
// Додаємо обробник подій на глобальний об'єкт windowwindow.addEventListener(Channels.changeUserRole, eventHandler);
};
// Функція для припинення прослуховування зміни ролі користувачаexportconst stopListeningToUserRoleChange = (): void => {
// Якщо обробник подій існує, видаляємо його та обнулюємо зміннуif (eventHandler) {
window.removeEventListener(Channels.changeUserRole, eventHandler);
eventHandler = null;
}
};
// Функція для надсилання події про зміну ролі користувачаexportconst emitChangeUserRole = (newRole: EnumRole): void => {
// Виводимо в консоль інформацію про подіюconsole.log(`Emit ${Channels.changeUserRole} - ${newRole}`);
// Створюємо нову подіюconst event = new CustomEvent(Channels.changeUserRole, {
detail: { role: newRole },
});
// Відправляємо подіюwindow.dispatchEvent(event);
};
Для реалізації сторінки редагування банківської картки, на якій враховано ролі користувачів, ми розпочнемо з встановлення механізму передплати на подію оновлення ролі. Це дозволить сторінці реагувати на зміни та адаптувати доступні функції редагування відповідно до поточної ролі користувача.
import React, { useEffect, useState } from'react';
import { Button, Card, List, Modal, notification } from'antd';
import { useDispatch, useSelector } from'react-redux';
import { getCardDetails } from'@modules/cards/store/features/cards/slice';
import { AppDispatch } from'@modules/cards/store/store';
import { userCardsDetailsSelector } from'@modules/cards/store/features/cards/selectors';
import { Transaction } from'@modules/cards/types';
import { events, variables, types } from'shared';
const { EnumRole } = types;
const { USER_ROLE } = variables;
const { onChangeUserRole, stopListeningToUserRoleChange } = events;
exportconst CardDetail = () => {
// Використання Redux для диспетчеризації та отримання стануconst dispatch: AppDispatch = useDispatch();
const cardDetails = useSelector(userCardsDetailsSelector);
// Локальний стан для ролі користувача та видимості модального вікнаconst [role, setRole] = useState(USER_ROLE);
const [isModalVisible, setIsModalVisible] = useState(false);
// Ефект для завантаження деталей картки при монтуванні компонента
useEffect(() => {
const load = async () => {
await dispatch(getCardDetails('1'));
};
load();
}, []);
// Функції для керування модальним вікномconst showEditModal = () => {
setIsModalVisible(true);
};
const handleEdit = () => {
setIsModalVisible(false);
};
const handleDelete = () => {
// Відображення повідомлення про видалення
notification.open({
message: 'Card delete',
description: 'Card delete success.',
onClick: () => {
console.log('Notification Clicked!');
},
});
};
// Ефект для передплати та відписки від подій зміни ролі користувача
useEffect(() => {
onChangeUserRole(setRole);
return stopListeningToUserRoleChange;
}, []);
// Умовний рендеринг, якщо деталі картки не завантаженіif (!cardDetails) {
return<div>loading...</div>;
}
// Функція визначення дій з урахуванням ролі користувачаconst getActions = () => {
switch (role) {
case EnumRole.admin:
return [
<Buttonkey="edit"type="primary"onClick={showEditModal}>
Edit
</Button>,
<Buttonkey="delete"type="dashed"onClick={handleDelete}>
Delete
</Button>,
];
case EnumRole.manager:
return [
<Buttonkey="edit"type="primary"onClick={showEditModal}>
Edit
</Button>,
];
default:
return [];
}
};
// Рендеринг компонента Card з деталями картки та діямиreturn (
<><Cardactions={getActions()}title={`CardDetails- ${cardDetails.cardHolderName} `}
>
{/* Відображення різних атрибутів картки */}
<p>PAN: {cardDetails.pan}</p><p>Expiry: {cardDetails.expiry}</p><p>Card Type: {cardDetails.cardType}</p><p>Issuing Bank: {cardDetails.issuingBank}</p><p>Credit Limit: {cardDetails.creditLimit}</p><p>Available Balance: {cardDetails.availableBalance}</p>
{/* Список останніх транзакцій */}
<Listheader={<div>Recent Transactions</div>}
bordered
dataSource={cardDetails.recentTransactions}
renderItem={(item: Transaction) => (
<List.Item>
{item.date} - {item.amount} {item.currency} - {item.description}
</List.Item>
)}
/>
<p><b>*For demonstration events from the host, change the user role.</b></p></Card>
{/* Модальне вікно для редагування */}
<Modaltitle="Edit transactions"open={isModalVisible}onOk={handleEdit}onCancel={() => setIsModalVisible(false)}
>
<p>Form edit card</p></Modal></>
);
Для налаштування розгортання програми через GitHub Actions створимо файл конфігурації .yml , який визначає робочий процес CI/CD. Ось приклад простого конфігу:
name: Build and Deploy Cards Project
# Цей workflow запускається при подіях push або pull request
# але тільки для змін в директорії 'packages/cards'.
on:
push:
paths:
- 'packages/cards/**'
pull_request:
paths:
- 'packages/cards/**'# Визначення задач (jobs) для виконанняjobs:
# Перше завдання: Встановлення залежностей
install-dependencies:
runs-on: ubuntu-latest # Завдання виконується на останній версії Ubuntu
steps:
- uses: actions/checkout@v2 # Выполняет checkout кода репозитория
- name: Set up Node.js # Встановлює Node.js версії 16
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Cache Node modules # Кешування Node модулів для прискорення збирання
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- name: Install Dependencies # Встановлення залежностей проекту через Yarn
run: yarn install
# Друге завдання: Тестування та складання
test-and-build:
needs: install-dependencies # Це завдання вимагає завершення завдання install-dependencies
runs-on: ubuntu-latest # Запускається на останній версії Ubuntu
steps:
- uses: actions/checkout@v2 # Виконує checkout коду репозиторію
- name: Use Node.js # використовує Node.js версії 16
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Cache Node modules # Кешування Node модулів
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- name: Build Shared Modules # Складання загальних модулів
run: yarn workspace shared build
- name: Test and Build Cards # Тестування та складання workspace Cards
run: |
yarn workspace cards test
yarn workspace cards build
- name: Archive Build Artifacts # Архівація артефактів складання для розгортання
uses: actions/upload-artifact@v2
with:
name: shared-artifacts
path: packages/cards/dist
# Третє завдання: Розгортання Cards
deploy-cards:
needs: test-and-build # Це завдання вимагає завершення завдання test-and-build
runs-on: ubuntu-latest # Запускається на останній версії Ubuntu
steps:
- uses: actions/checkout@v2 # Виконує checkout коду репозиторію
- name: Use Node.js # Використовує Node.js версії 16
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Cache Node modules # Кешування Node модулів
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- name: Download Build Artifacts # Скачування артефактів збирання з попереднього завдання
uses: actions/download-artifact@v2
with:
name: shared-artifacts
path: ./cards
- name: Deploy to Server # Розгортання артефактів на сервері за допомогою SCP
uses: appleboy/scp-action@master
with:
host: ${{ secrets.HOST }}
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: 'cards/*'
target: '/usr/share/nginx/html/microfrontend/apps'
Тут ми можемо додати такі функції, як версіонування та A/B тестування, керуючи ними через Nginx.
У результаті, у нас виходить система, де кожна команда, яка працює над різними модулями, має свою програму в структурі мікрофронтенду.
Цей підхід прискорює процес складання, оскільки більше не потрібно очікувати на перевірку всього додатка. Код можна оновлювати частинами та проводити регресивне тестування для кожного окремого компонента.
Також значно зменшується проблема із конфліктами злиття (мердж-конфліктами), оскільки команди працюють над різними частинами проекту незалежно одна від одної. Це підвищує ефективність роботи команд та спрощує процес розробки загалом.
Чому typeof null === «object» у сучасному прочитанні
Завдання унарного оператора typeof рядкове подання типу операнда. Інакше кажучи, typeof 1 поверне рядок "number", а typeof "" поверне "string". Усі можливі значення типів, що повертаються оператором типувикладені в специфікації ECMA-262 – 13.5.1 . За задумом, що повертається оператором, значення має відповідати прийнятим у тій же специфікації типів даних. Однак, при детальному розгляді, можна помітити, що typeof null повинен повертати "object", незважаючи на те, що Null це цілком собі самостійний тип, він описаний в розділі 6.1.2 . Причина тому – звичайний людський фактор, або просто невинна помилка в коді. Як ця помилка могла статися, спробуємо розібратися у цій статті.
Mocha
Почати варто, мабуть, з самого початку JavaScript, і саме прототипної мови Mocha, створеної Бренданом Айком в 1995-му році всього за 10 днів, який пізніше був перейменований в LiveScript, а ще пізніше, в 1996-му, став відомим нам сьогодні JavaScript.
На жаль, вихідний код Mocha не був опублікований і ми не знаємо, як саме він виглядав у далекому 1995-му, проте, у коментарях до статті в блозі доктора Алекса Раушмайєра, Айк писав, що використовував техніку “Discriminated Union”, вона ж – “Tagged Union”, де він використовував struct із двома полями.
У самій статті, Алекс Раушмайер наводить приклад коду движка SpiderMonkey (використовується в Mozilla Firefox) від 1996-го року
JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
JSType type = JSTYPE_VOID;
JSObject *obj;
JSObjectOps *ops;
JSClass *clasp;
CHECK_REQUEST(cx);
if (JSVAL_IS_VOID(v)) {
type = JSTYPE_VOID;
} elseif (JSVAL_IS_OBJECT(v)) {
obj = JSVAL_TO_OBJECT(v);
if (obj &&
(ops = obj->map->ops,
ops == &js_ObjectOps
? (clasp = OBJ_GET_CLASS(cx, obj),
clasp->call || clasp == &js_FunctionClass)
: ops->call != 0)) {
type = JSTYPE_FUNCTION;
} else {
type = JSTYPE_OBJECT;
}
} elseif (JSVAL_IS_NUMBER(v)) {
type = JSTYPE_NUMBER;
} elseif (JSVAL_IS_STRING(v)) {
type = JSTYPE_STRING;
} elseif (JSVAL_IS_BOOLEAN(v)) {
type = JSTYPE_BOOLEAN;
}
return type;
}
Алгоритм хоч і відрізняється від оригінального коду Mocha, добре ілюструє суть помилки. У ньому просто немає перевірки на тип Null. Натомість у разі val === "null"алгоритм потрапляє у гілку else if (JSVAL_IS_OBJECT(v)) і повертає JSTYPE_OBJECT
Чому саме “object”?
Справа в тому, що значення змінної в ранніх версіях мови було 32-бітним числом без знака ( uint_32), Де перші три біти, якраз, і вказують на тип змінної. За такої схеми були прийняті такі значення цих перших трьох бітів:
000: object – змінна є посиланням на об’єкт
001: int – змінна містить 31-бітове ціле число
010: double – змінна є посиланням на число з точкою, що плаває
100: string – Змінна є посиланням на послідовність символів
110: boolean – Змінна є булевим значенням
У свою чергу Null був покажчиком на машинний nullptr, який, у свою чергу, виглядає, як 0x00000000
Тому перевірка JSVAL_IS_OBJECT(0x00000000) повертає true, адже перші три біти рівні 000, що відповідає типу object.
Спроби виправити баг
Пізніше ця проблема була визнана багом. У 2006-му році Ейх запропонував скасувати оператор typeof і замінити на функцію type(), яка б враховувала, в тому числі і Null ( архівна копія пропозиції ). Функція може бути вбудованою або бути частиною опціонального пакета reflection. Однак, у будь-якому випадку, такий фікс не був би сумісний з попередніми версіями мови, що породило б безліч проблем з вже існуючим JavaScript кодом, написаним розробниками по всьому світу. Потрібно було б створювати механізм перевірки версій коду та/або опції мови, що настроюються, що не виглядало реалістичним.
У підсумку пропозиція не була прийнята, а оператор typeof у специфікації ECMA-262 так і залишився у своєму оригінальному вигляді.
Ще пізніше, 2017-го було висунуто ще одну пропозицію Builtin.is and Builtin.typeOf . Основна мотивація в тому, що оператор instanceof не гарантує правильну перевірку типів змінних із різних реалмів. Пропозиція була пов’язана безпосередньо з Null, проте, його текст передбачав виправлення і цього бага у вигляді створення нової функції Builtin.typeOf(). Пропозиція так само була прийнята, т.к. окремий випадок, продемонстрований у мотиваційній частині, хоч і не дуже елегантно, але може бути вирішений існуючими методами.
Сучасний Null
Як я писав вище, баг з’явився в 1995 році в прототипній мові Mocha, ще до появи самого JavaScript і до 2006 року Брендан Ейх не залишав надій виправити його. Проте з 2017-го ні розробники, ні ECMA більше не намагалися цього зробити. З тих пір мова JavaScript стала набагато складнішою, як і її реалізації в популярних двигунах.
SpiderMonkey
Від коду SpiderMonkey, який публікував Алекс Раушмайєр у своєму блозі 2013 року, не залишилося і сліду. Тепер двигун (на момент написання статті, версія FF 121) бере значення типувід заздалегідь визначеного тегу змінної
JSType js::TypeOfValue(const Value& v){
switch (v.type()) {
case ValueType::Double:
case ValueType::Int32:
return JSTYPE_NUMBER;
case ValueType::String:
return JSTYPE_STRING;
case ValueType::Null:
return JSTYPE_OBJECT;
case ValueType::Undefined:
return JSTYPE_UNDEFINED;
case ValueType::Object:
returnTypeOfObject(&v.toObject());
#ifdef ENABLE_RECORD_TUPLEcase ValueType::ExtendedPrimitive:
returnTypeOfExtendedPrimitive(&v.toExtendedPrimitive());
#endifcase ValueType::Boolean:
return JSTYPE_BOOLEAN;
case ValueType::BigInt:
return JSTYPE_BIGINT;
case ValueType::Symbol:
return JSTYPE_SYMBOL;
case ValueType::Magic:
case ValueType::PrivateGCThing:
break;
}
ReportBadValueTypeAndCrash(v);
}
Тепер двигун точно знає, якого типу змінна передана оператор, т.к. після декларування, об’єкт змінної містить біт, що вказує на її тип. Для Null оператора повертає значення JSTYPE_OBJECTявним чином, як того вимагає специфікація
enumJSValueType :uint8_t {
JSVAL_TYPE_DOUBLE = 0x00,
JSVAL_TYPE_INT32 = 0x01,
JSVAL_TYPE_BOOLEAN = 0x02,
JSVAL_TYPE_UNDEFINED = 0x03,
JSVAL_TYPE_NULL = 0x04,
JSVAL_TYPE_MAGIC = 0x05,
JSVAL_TYPE_STRING = 0x06,
JSVAL_TYPE_SYMBOL = 0x07,
JSVAL_TYPE_PRIVATE_GCTHING = 0x08,
JSVAL_TYPE_BIGINT = 0x09,
#ifdef ENABLE_RECORD_TUPLE
JSVAL_TYPE_EXTENDED_PRIMITIVE = 0x0b,
#endif
JSVAL_TYPE_OBJECT = 0x0c,
// This type never appears in a Value; it's only an out-of-band value.
JSVAL_TYPE_UNKNOWN = 0x20
};
V8
Подібний підхід застосовується і в двигуні V8 (на момент написання статті, версія 12.2.165 ). Тут Null є так званим типом Oddball, тобто. об’єкт типу Null инциализируется ще до виконання JS-коду, проте наступні посилання значення Null ведуть цей єдиний об’єкт.
Крім зони Isolate , посилання на саме значення змінної та enum типу, він так само, явно приймає значення toString, toNumber і typeof, які далі буде зберігати всередині класу. Що дозволяє при ініціалізації глобальної купи (Heap) визначити потрібні значення цих параметрів Oddball
У розробці часто виникають ситуації, коли точність типів та небажання допускати неясності в коді стають першорядними завданнями. У таких випадках розробникам доводиться шукати інструменти, що надають максимальну ясність та строгість у визначенні даних. Один із таких інструментів — ключове слово as const. У цій статті ми розглянемо, як as constможе підвищити рівень суворості та передбачуваності, а також розглянемо практичні приклади його використання для створення незмінних та точних типів.
Докладніше про “as const”
Коли ви використовуєте as const змінну або значення, TypeScript уточнює тип цієї змінної до її точного значення або комбінації літеральних типів. Це часто використовується для створення незмінних значень та гарантування того, що TypeScript розглядатиме значення як конкретні літерали, а не розширюватиме типи.
Приклад :
Уявіть, що нам потрібно написати функцію для відкриття вхідних дверей до будинку. Пишемо функцію, яка приймає параметром ключ, та проводить з ним будь-які операції.
Але коли ми намагаємося використовувати функцію openDoor з ключем з нашого гаманця, чомусь відбувається таке:
const wallet = {
key: "open_door_pls"
}
const openDoor = (key: "open_door_pls") => {
//...
}
openDoor(wallet.key) // ERROR: Argument of type 'string' is not assignable to parameter of type '"open_door_pls"'
Чому ж ми потрапили до такої ситуації?
Вся справа в тому, що wallet.key у нас ніяк не прив’язаний до значення "open_door_pls”, і по суті є просто елементом з типом string, значення якого можна легко змінити на інше:
const wallet = {
key: "open_door_pls"
}
wallet.key = "cucumber"// код отрабатывает без ошибок
Щоб уникнути такої поведінки, і зробити всі елементи wallet повноцінними, незмінними ( readonly) значеннями, ми можемо скористатися конструкцією as const:
const wallet = {
key: "open_door_pls"
} asconst
wallet.key = "cucumber"// ERROR: Cannot assign to 'key' because it is a read-only property.
Елементи wallet прив’язалися до своїх значень і тепер мають прапор readonly.
Що зрештою вирішило нашу проблему з параметром функції openDoor:
При використанні as constкомпілятора на будь-якому рівні вкладеності вкаже, що програміст намагається змінити константу.
const car = {
name: "Porshe Cayenne",
equipment:{
engine: "MDC.AB"
}
} asconst;
car.equipment.engine = "F8CV"; // ERROR: Cannot assign to 'engine' because it is a read-only property.
Заміна enum-ам?
as constчудово підходить як альтернатива enum.
Про мінуси enum можна докладно почитати у цій статті.
Ще одна дійсно крута особливість as const полягає в тому, що використання as const дозволяє бути дуже гнучким у поводженні з ключовим об’єктом:
const friendsDict = {
Alfred: "101 Pine Road, Toronto, ON M5A 1A1, Canada",
Liam: "777 Sycamore Lane, Tokyo, 100-0001, Japan",
Mia: "666 Willow Street, Paris, 75001, France",
} asconst;
type FriendName = keyof typeof friendsDict; // "Alfred" | "Liam" | "Mia"type FriendAddress = (typeof friendsDict)[keyof typeof friendsDict];
//"101 Pine Road, Toronto, ON M5A 1A1, Canada" | "777 Sycamore Lane, Tokyo, 100-0001, Japan" | "666 Willow Street, Paris, 75001, France"
Резюмуючи
Ми розглянули конструкцію as const у TypeScript та її роль у створенні більш строгих та передбачуваних типів даних. Сподіваюся, що ця стаття допомогла вам краще зрозуміти, як використання as const може підвищити рівень безпеки та ясності вашого коду.
Таким чином, впровадження as const у ваш код може бути ключем до полегшення його підтримки у майбутньому та зменшення ймовірності помилок. Користуйтеся цим інструментом з розумом, і ваш код стане чистішим, надійнішим та легко підтримуваним!
Попит на функції пошуку зростає, і багато розробників намагаються впровадити їх у свої програми. Однак створення таких додатків з нуля – складне та трудомістке завдання. На щастя, існує безліч бібліотек з відкритим вихідним кодом, які дозволяють звільнити розробників від цього тягаря.
У цьому посібнику читач знайде перелік найкращих пошукових пакетів для JavaScript. У статті буде проведено огляд та порівняння пошукових пакетів з урахуванням швидкості, простоти використання, типу пропонованого пошуку, бази даних та інших характеристик. Крім того, читачі зрозуміють, що таке функціональність пошуку та навіщо вона потрібна.
Функціональність пошуку
Як застосувати пошукову функціональність JavaScript
Пошукові пакети для JavaScript
Функціональність пошуку
Один з ключових способів покращити дизайн сайту, щоб допомогти користувачам знайти потрібну інформацію, – це зробити програму доступною для пошуку за допомогою функції пошуку. Користувачі можуть швидко знаходити контент, задаючи пошук за певними фразами на сайті або у додатку, не вникаючи та не вивчаючи всю структуру. Нижче наведено причини, з яких це доцільно:
Поліпшення залучення користувачів та зниження відсотка відмов
Економія часу користувачів на пошук товарів
Підвищення темпи зростання з допомогою продажу більшої кількості товарів та послуг.
Існує три різні типи пошуку:
Автозаповнюваний пошук
Функція пошукової системи, яка відображає ключові фрази та інші пропозиції в режимі реального часу. Її також називають передиктивним пошуком чи автоповідомленням. Пропозиції ґрунтуються на ключовому слові, яке користувач вводить у поле пошуку.
Пошукова система пропонує кілька прогнозів на основі введеного запиту та зіставляє його з даними у пошуковому індексі. Наприклад, якщо ви введете слово “електроніка” на веб-сторінці, присвяченій електроніці, пошукова система запропонує “електроніку” на цій сторінці.
Повнотекстовий пошук
Це функція пошукової системи, що дозволяє ефективно знаходити дані, коли користувач розуміє потрібну інформацію лише частково. Система шукає текст у великих текстових даних, сховище яких перебуває у електронному вигляді. У відповідь система видає результати, які містять деякі або всі слова із запиту, а не точні збіги.
Наприклад, якщо в полі пошуку ввести слово “пшениця”, результати будуть містити не тільки слово “пшениця”, але й елементи, пов’язані з ним, наприклад “хліб” та “паста”.
Нечіткий пошук
Це функція пошукової системи для пошуку рядків, що майже збігаються із заданим рядком. Її також називають нечітким пошуком. Нечіткий пошук повертає результати, що базуються на приблизному збігу пошукового запиту. Крім того, він діє як коректор орфографії та робить пошук гнучким. Він може виконувати вставку, видалення та заміну запитів.
Наприклад, при пошуку “strng” буде запропоновано “string”, а при пошуку “husttle” – “hustle”.
Як застосувати пошукову функціональність JavaScript
Для пошуку інформації на сайті користувачам потрібна вкладка пошуку, яка забезпечує доступ до функцій пошуку. Пошукова функціональність JavaScript дозволяє користувачам сайту знаходити вміст за певними фразами без необхідності розумітися на структурі сайту.
Але реалізація пошукової функціональності з нуля може зайняти багато часу та роботи. Веб-розробка стала простішою, оскільки розробникам не потрібно щоразу вводити складний код, щоб додати функцію пошуку. Деякі компанії пропонують безкоштовний доступ до своїх пошукових програм.
Пошукові програми дозволяють заощадити час та ресурси, забезпечуючи кращий контроль для підприємства.
Пошукові пакети для JavaScript
Нижче наведено пошукові бібліотеки JavaScript для створення продуктивного та миттєвого пошуку. У цьому розділі ми розглянемо пошукові пакети JavaScript та порівняємо бібліотеки за їхніми можливостями. Без зайвих слів, почнемо.
Fuse.js – це бібліотека, побудована на JavaScript, яка використовується для додавання полегшеного пошуку на стороні клієнта у браузері користувача. Функції пошуку корисні на веб-сайтах та програмах, щоб користувачі могли ефективно знаходити те, що їм потрібно. Fuse.js надає можливості нечіткого пошуку для програм та веб-сайтів.
Fuse.js можна використовувати і на бекенді, оскільки він не має залежностей від DOM. З іншого боку, Fuse.js може бути повільним під час роботи з величезними масивами даних. Визначаючи, який обсяг даних надто великий для Fuse.js, майте на увазі, що вам доведеться завантажувати весь набір даних на стороні клієнта, оскільки Fuse.js необхідний доступ до всього набору даних. Fuse.js не дуже добре працює з великими наборами даних.
Як застосувати Fuse.js
Щоб використовувати Fuse.js, вам не потрібно створювати окремий бекенд лише для пошуку. При створенні цієї бібліотеки основними міркуваннями були простота та продуктивність.
Fuse.js – це швидка та проста у використанні бібліотека, і реалізація функції пошуку на JavaScript стала легкою. При реалізації пошукової функціональності Fuse.js потребує конфігурацій, які дозволяють вносити зміни та створювати потужні рішення.
Щоб встановити Fuse.js , вам потрібно спочатку ініціалізувати проект Node.js. Потім додайте його у свій проект за допомогою npm:
$ npm install --save fuse.js
Якщо ви шукаєте потужний, легкий пошуковий пакет Javascript з функцією нечіткого пошуку, fuse.js– найкращий варіант.
Apache Solr – це пошукова система з відкритим вихідним кодом, яка використовується для створення пошукових програм. Solr побудований на базі пошукової бібліотеки Apache Lucene . Він готовий до роботи на підприємствах, має високу масштабованість і швидкість. Solr дозволяє створювати складні та високопродуктивні додатки.
Apache Solr збирає структуровані, напівструктуровані та неструктуровані дані з різних джерел у сховищі, індексує їх, а потім робить їх доступними для швидкого пошуку. Бібліотека може працювати з великими масивами даних. Вона підтримує розраховану на багато користувачів архітектуру, що дозволяє масштабувати, розподіляти і підтримувати індекси для великих додатків.
Балансування навантаження на запити, автоматизація завдань, централізоване налаштування, розподілене миттєве індексування, готова до масштабування інфраструктура – ось лише деякі з можливостей Solr.
Як застосувати Apache Solr
Apache Solr дає змогу розширити можливості користувачів. Це стабільна, надійна та стійка до відмови пошукова платформа. Розробники бекенда можуть скористатися такими функціями, як об’єднання, кластеризація, можливість імпорту документів у різних форматах та багато інших.
Solr – це платформа повнотекстового пошуку з REST-подібним API. Розробники можуть легко передавати документи через JSON, XML і HTTP. Apache Solr простий у використанні. Його легко встановити та налаштувати. Він орієнтований працювати з текстом, оптимізований для пошуку, а результати пошуку сортуються по релевантності.
Щоб встановити та розпочати роботу з apache solr. Перейдіть на сайт Solr, щоб отримати інструкції зі встановлення .
Lunr.js – це пакет повнотекстового пошуку для JavaScript. Він дозволяє здійснювати всебічний пошук набору даних. Він невеликий, потужний і, що найголовніше, простий у використанні.
Повнотекстовий пошук – це просунута техніка пошуку у базі даних. Ця техніка зазвичай використовується для швидкого пошуку документів або записів за ключовим словом. Вона також дозволяє ранжувати документи щодо релевантності за допомогою системи оцінок.
Як застосувати Lunr.js
Lunr.js працює на стороні клієнта за допомогою веб-застосунків, створених на JavaScript. Він здійснює пошук даних у індексі, створеному за клієнта. Це дозволяє уникнути складних зворотних мережевих викликів між клієнтом та сервером.
Lunr.js – це більш компактна альтернативна бібліотека Apache Solr. Вона має повнотекстову підтримку 14 мов та пропонує нечітке зіставлення термінів. Lunr.js – одна з найкращих пошукових бібліотек для створення додатків на JavaScript. Реалізувати функціональність повнотекстового пошуку шляхом жорсткого кодування дуже складно, особливо у браузері. На щастя, lunr.jsце потужний і простий у використанні інструмент для додавання пошукової функціональності.
Lunr.js – гарний вибір для миттєвого пошуку. Це пов’язано з тим, що він підтримує потужну систему ранжирування та систему плагінів для обробки ключових слів під час індексації та запиту.
Щоб розпочати роботу з lunr.js , спочатку ініціалізуйте проект Node.js. Потім установіть пакет lunr.jsза допомогою npm:
$ npm install lunr
ElasticLunr.js – це легкий пакет повнотекстового пошуку із вбудованим JavaScript для пошуку у браузері та офлайн. Він був створений на основі Lunr.js, але є більш гнучким. Він пропонує додаткові можливості налаштування та швидшу обробку запитів.
Як застосувати ElasticLunr.js
Пошуковий пакет не вимагає розгортання та пропонує функції автономного пошуку. Він легко працює з програмами, створеними за допомогою гібридних фреймворків JavaScript та Cordova. Швидкість – важлива характеристика, elasticlunr.jsдуже швидка в порівнянні з тим lunr.js, що полегшує пошук.
Щоб розпочати роботу з ElasticLunr.js, перейдіть на цей сайт та отримайте інструкції з встановлення.
InstantSearch.js – це бібліотека пошуку з відкритим кодом для Vanilla JavaScript. Вона дозволяє швидко створити пошуковий інтерфейс, використовуючи пошуковий API Algolia у вашому зовнішньому додатку. Пошуковий пакет створює продуктивні та миттєві пошукові можливості. Algolia – це платформа пошуку як послуги, яка дозволяє вам легко впровадити пошук у реальному часі у вашу програму за допомогою настроюваних, попередньо створених віджетів та інструментів для створення блоків.
Як застосувати InstantSearch.js
InstantSearch прагне максимально спростити розробку відмінних пошукових програм, пропонуючи повноцінне пошукове середовище. InstantSearch пропонує зовнішні віджети, які ви можете зібрати в унікальний пошуковий досвід, щоб задовольнити значну частину цієї величезної мети. Також використовуйте InstantSearch, якщо вам потрібно налаштувати компоненти на власний розсуд.
FlexSearch – це бібліотека повнотекстового та незалежного пошуку на JavaScript для веб-браузерів та Node.js. Це одна з найшвидших пошукових бібліотек для розробників JavaScript завдяки алгоритму скорингу, що називається контекстним пошуком .
Як застосувати FlexSearch
Якщо говорити про швидкість, то FlexSearch перевершує всі інші пошукові бібліотеки. Крім того, вона надає гнучкі можливості пошуку, такі як частковий збіг, фонетичні перетворення та пошук по кількох полях. FlexSearch пропонує безліч варіантів налаштування для підвищення ефективності використання пам’яті та швидкості роботи.
List.js – це проста у використанні, потужна та надшвидка бібліотека пошуку для Vanilla JavaScript. Це ідеальна бібліотека для додавання пошуку, сортування, фільтрів та гнучкості до таблиць, списків та HTML-елементів. List.js створена так, щоб бути невидимою та працювати з існуючим HTML.
Як застосувати List.js
List.js простий і непомітний, немає залежностей. Він малий, дуже швидкий, легко застосовується до існуючих HTML-елементів та обробляє тисячі елементів. Бібліотека пошуку працює зі списками, таблицями та іншими HTML-елементами, наприклад, <table>, <ul>, <div>і т.д.
JS Search – це потужна бібліотека пошуку на стороні клієнта для об’єктів JavaScript та JSON. Вона є полегшеною реалізацією Lunr.js і має широкі можливості налаштування для підвищення продуктивності.
Як застосувати JS Search
JS Search – це швидкий та простий у використанні пошуковий пакет. Щоб почати роботу з js-search , ви можете встановити його за допомогою npm:
$ npm install js-search
MiniSearch – це крихітна, легка, повнотекстова та потужна бібліотека пошуку для JavaScript. Вона призначена для роботи як у Node.js, так і у веб-браузері.
Як застосувати MiniSearch
MiniSearch має функції повнотекстового пошуку, серед яких (нечіткий пошук, пошук за префіксами, ранжування тощо). Бібліотека пошуку також може працювати в автономному режимі та швидко обробляти запити без затримок мережі.
MiniSearch має ефективний індекс пам’яті, дає точний збіг, має механізм автопідбору пошукових запитів та не має зовнішніх залежностей.
У статті було розглянуто найкращі пошукові пакети на JavaScript, які має знати кожен розробник. Створити пошукову функціональність з нуля не так просто. Використання будь-якого з пакетів пошуку заощадить вам значну кількість часу та роботи. Це керівництво допомогло вам дізнатися про деякі поширені пакети пошуку в JavaScript та їх можливості. Бібліотеки пошуку покращать роботу сайту або програми, оскільки користувачі зможуть легко отримати потрібну їм інформацію.
Регенеративні фінанси (Regenerative finance, ReFi) – концепція та напрямок децентралізованих фінансів (DeFi), в рамках якого створюються комплексні економічні моделі. Останні, крім матеріальної винагороди, припускають сприятливий вплив на довкілля та вирішення суспільних проблем.
Головна мета ReFi полягає в підтримці зростання регенеративної економіки, наголошуючи на екологічному аспекті, соціальному благополуччі та ресурсній стійкості, також сприяючи великомасштабному співробітництву з таких глобальних питань, як зміна клімату та втрата біорізноманіття.
Основні принципи та концепція регенеративних фінансів запропонована у 2015 році Джоном Фуллертоном із Capital Institute.
ReFi має багато спільного з економікою замкнутого циклу, оскільки проекти цього сегменту також фокусуються на відновленні ресурсів та чистої енергії.
Традиційні економічні системи часто критикують за нерівномірний розподіл ресурсів та пріоритизацію короткострокового прибутку над довгостроковою стійкістю.
У свою чергу, ReFi властиві:
всеосяжний підхід, що бере до уваги взаємозалежність економічних, соціальних та екологічних факторів;
акцент на сталому розвитку — особливий акцент робиться на фінансуванні екологічних ініціатив та підприємств, пов’язаних із відновлюваними джерелами енергії;
фокус на соціальній складовій — ReFi покликані зменшити проблему економічної нерівності та підвищити добробут суспільства (наприклад, завдяки фінансуванню ініціатив щодо розширення доступу до освіти, створення робочих місць та будівництва недорогого житла);
зміщення акценту від довгострокових вигод до сталого довгострокового ефекту;
заохочення відкритості, прозорості та підзвітності;
активну участь спільнот у ухваленні рішень.
Для реалізації поставленої мети ReFi-проекти можуть використовувати залучений капітал, у тому числі за допомогою токенсейлів, а також принципи ДАО. Відповідні платформи зазвичай ґрунтуються на смарт-контрактах, що ріднить сегмент зі сферою DeFi.
ReFi також перетинається з рухом децентралізованої науки (DeSci), який використовує Ethereum та інші екосистеми для фінансування, створення, зберігання та розповсюдження наукових знань.
Які галузі охоплює ReFi?
Один із головних напрямів ReFi — токенізація вуглецевих кредитів , покликана сприяти скороченню шкідливих викидів в атмосферу.
Добровільний вуглецевий ринок (Voluntary carbon market, VCM) — це механізм фінансування ініціатив, які мають перевірений позитивний вплив на викиди CO₂. Після перевірки ці проекти отримують активи, іменовані вуглецевими кредитами, які можуть продавати окремим особам та організаціям, бажаючим підтримати діяльність у сфері клімату.
Перехід VCM на новий цифровий вуглецевий ринок (Digital carbon market, DCM) покликаний відкрити доступ широкого кола користувачів, усунути посередників, сприяти зростанню ліквідності та підвищення швидкості операцій.
Значними елементами ландшафту DCM є проекти Verra, Gold Standard, Toucan Protocol, C3, Moss.Earth, Klima Infinity, Senken та Nori. Вони сприяють інвестуванню у вуглецеві кредити та сприяють організації ініціатив щодо збереження клімату.
Відомий проект ReFi-екосистеми – Celo. Він позиціонується як «мобільно-орієнтований» та «вуглецево-негативний» блокчейн. Ініціатива “Ультразелені гроші” заснована на механізмі спалювання токенів, який відправляє 20% комісій за транзакції в мережі до фонду компенсації викидів вуглецю, що робить токен CELO дефляційним.
Регенеративні фінанси також покликані допомогти зберегти історичні записи та артефакти культурної спадщини , використовуючи технологію блокчейн та NFT. Зокрема, на цьому напрямі сфокусовано компанію Monuverse.
До ReFi також можна віднести проекти, що спеціалізуються на соціально відповідальному кредитуванні . За допомогою подібних платформ позичальники можуть отримати доступ до фінансування освітніх та інших ініціатив, що відповідають «регенеративним принципам».
Web3 також уможливлює випуск і торгівлю децентралізованими «зеленими облігаціями» . Останні дають можливість широкому колу користувачів брати участь у фінансуванні екологічних ініціатив, де блокчейн та смарт-контракти роблять платежі прозорими та автоматизують нарахування відсотків на вкладені кошти.
Які ризики властиві ReFi?
Як і будь-якому іншому сегменту криптоіндустрії, ReFi властиві ризики та підводні камені.
Наприклад, під питанням може бути фінансова стійкість проектів сегмента децентралізованих фінансів. Критики концепції стверджують, що орієнтовані на довгострокову перспективу вкладення з фокусом на екологію та соціальний ефект поступаються за рентабельністю інвестиціям у традиційні проекти.
Наступний аспект — складність та трудомісткість оцінки соціальних та екологічних наслідків фінансових рішень. ReFi-проекти можуть пом’якшити проблему шляхом впровадження універсальних стандартів використання технологій, а також за допомогою блокчейну та інших передових технологій для підвищення прозорості моніторингу процесів.
Важливо враховувати і потенційні регуляторні перешкоди , які можуть виникнути через прогалини в нормативно-правовій базі. Шляхом вирішення проблеми може стати конструктивний діалог з владою, традиційними фінансовими установами та бізнес-структурами, а також просвітницька робота для популяризації ReFi.
Немаловажна й проблема масштабування — ReFi-проекти можуть надавати обмежений вплив на глобальному рівні через труднощі із залученням коштів та логістичних труднощів. Змінити ситуацію може розвиток технологій, створення глобальних мереж та спільних підприємств, а також партнерства із спільнотами, що поділяють принципи регенеративних фінансів.
Деякі ReFi-проекти ставлять високі цілі, пропонуючи розпливчасті формулювання про те, як ці цілі будуть досягнуті. Найчастіше подібні компанії прагнуть отримати зиск на хайпі навколо екологічних та інших соціальних ініціатив.
Інвесторам слід проявляти особливу пильність, якщо:
інформація про токеноміку недостатня;
значна частина пропозиції монет зосереджена руках засновників і вузького кола ранніх інвесторів;
у проекту відсутня детальна дорожня карта з чітко окресленими датами та запланованими діями для реалізації поставлених цілей.
Перш ніж вкладати кошти у нову ReFi-ініціативу, учасникам ринку слід уважно вивчити бекграунд команди проекту.
Якщо платформа пропонує користувачеві розкрити сид-фразу – це шахрайство, націлене на спустошення гаманця. Слід взаємодіяти лише з перевіреними проектами, наприклад, із тими, хто залучив інвестиції від відомих венчурних фірм.
Як співвідносяться ReFi та DeFi?
Використовуючи блокчейн та смарт-контракти, DeFi та ReFi багато в чому схожі між собою. Однак, будучи тісно взаємопов’язаними напрямками, вони служать дещо різним цілям.
У контексті криптоіндустрії в цілому DeFi можна як більш широку категорію, де ReFi – її складова частина. Цей напрямок розвивається паралельно з іншими безпосередньо не пов’язаними з фінансами новими підсегментами на кшталтDeSci,DePINіDeSoc.
Які перспективи сегмента ReFi?
За всієї значущості концепції регенеративних фінансів ще рано говорити про практичну користь відповідних проектів. Головна причина в низькому інтересі широкої аудиторії — поки що мало хто готовий брати участь у подібних ініціативах.
Наприклад, добовий обсяг торгів токеном Moss Carbon Credit (MCO2) складає $39 400, за даними CoinGecko на 23 грудня. Актив низьколіквідний і не представлений на популярних платформах, таких як Binance або OKX.
Модель ReFi ще слабо поширена у реальних проектних розробках. Представленими на ринку рішеннями поки що користується лише невелика кількість по-справжньому зацікавлених учасників.
Прихильники регенеративних фінансів намагаються вирішити важливе, певною мірою утопічне завдання. Однак розвиток сегменту та популяризація концепції з часом можуть зробити ReFi набагато популярнішим, змінивши ставлення громадськості до криптовалютів загалом.
Вилучення прибутку не є головною метою при взаємодії із платформами регенеративних фінансів. Рух ліквідності розуміється як ланка в ланцюзі та засіб для комплексного вирішення екологічних, суспільних та наукових завдань.
Значна частина громадськості, як і раніше, сприймає використання криптоактивів у відриві від «реального світу». У багатьох блокчейн-індустрія асоціюється тільки зі спекуляціями монетами, що не мають цінності, для швидкого збагачення. З можливою популяризацією ідеї ReFi у сфері децентралізованих фінансів і криптовалют в цілому з’являється шанс на оновлення іміджу.
Real World Assets (RWA) — термін, що означає ринок активів «реального світу», випущених у формі токенів на блокчейні. У RWA включають нерухомість, право власності, предмети мистецтва, коштовності та метали, традиційні фінансові інструменти на ринках капіталу.
Концепція має на увазі токенізацію вже існуючих активів для перенесення цінності в децентралізовані фінансові програми, скорочення витрат або підвищення ефективності управління коштами на традиційних ринках.
Що належать до RWA?
Ринок Real World Assets ще остаточно не сформований, але вже встиг розділитись на категорії проектів, що належать до загального тренду токенізованих активів реального світу.
Централізовані стейблкоіни. Популярні монети, такі як USDT від Tether або USDC від Circle, за своєю природою є RWA. Компанії-емітенти таких токенів блокують фіатні валюти, державні або комерційні цінні папери на своїх рахунках як забезпечення. Потім вони випускають стейблкоіни, що є своєрідною токенізацією.
Приватне кредитування Одним із учасників цього сектору єДАНО MakerDAO, емітент стейблкоіна DAI. У середині 2022 року організація відкрила кредитну лінію на $100 млн у токенах для американського Huntingdon Valley Bank. Як забезпечення банк заклав позабалансові кредити. Свої розробки на цьому ринку запропонував і емітент стейблкоіна USDC — він запустив протокол з відкритим кодом Perimeter, який є основою для створення токенізованих ринків кредитування.
Державні облігації. Сектор представлений компаніями, що випускають стейблкоіни, у забезпеченні яких лежать казначейські облігації будь-якої країни. Наприклад, Ondo Finance випускає стабільну монету USDY під заставу короткострокових казначейських облігацій США. Сюди слід віднести проект Mountain Protocol, що запустив стейблкоїн USDM на базі блокчейна Ethereum. Він також забезпечений казначейськими облігаціями, які приносять пасивний дохід у розмірі 5% річних.
Токенізовані цінних паперів. Новатором у цьому секторі стала біржа криптовалют Bitfinex. У вересні 2021 року розпочала роботу її дочірня платформа Bitfinex Securities, орієнтована на RWA-ринок. У жовтні 2023 року вона випустила першу токенізовану облігацію з трирічним періодом погашення та ставкою 10%.
Інтерес до сектору виявили й інституційні інвестори. Найбільший банк Великобританії HSBC планує у 2024 році запропонувати клієнтам сервіс для зберігання токенізованих цінних паперів. У жовтні 2023 року конгломерат JPMorgan запустив Tokenized Collateral Network (TCN), сервіс конвертації акцій у цифрові активи. У розробці брали участь фінансові гіганти BlackRock та Barclays.
Нерухомість. У січні 2023 року конгломерат Société Générale зайняв у MakerDAO $7 млн у DAI. Як актив для забезпечення кредиту виступили регульовані законодавством Франції іпотечні облігації на $40 млн.
Сертифікати вуглецевих кредитів . 2022 року набрав популярність ринок «зелених» сертифікатів. Наприклад, проект KlimaDAO, який інвестував мільярдер Марк Кьюбан, пропонував криптокористувачам вуглецеві кредити. Теоретично зростання цін такі сертифікати мало спонукати компанії, забруднюючі довкілля, скоротити викиди. Однак це дуже нерозвинений ринок, повний шахрайства.
Твори мистецтва та предмети колекціонування. Цей сегмент представлений за допомогою ринку NFT. У 2021 році відразу кілька компаній та інвесторів перенесли цінність у блокчейн шляхом токенізації картин відомих художників. Так, швейцарський криптобанк Sygnum випустив серію NFT на основі роботи Пабло Пікассо «Дівчинка у береті», розділивши її на 4000 токенів. А засновник Tron Джастін Сан купив картини Пікассо та Енді Уорхола за $22 млн із наміром токенізувати їх через JUST NFT Fund.
Дорогоцінні метали. Учасниками цієї галузі ринку RWA є емітенти стейблкоїнів, які мають у забезпеченні дорогоцінні метали у фізичній формі. Як приклад можна навести токени від компанії Paxos або XAUT від Tether.
Які перспективи ринку RWA?
Головний аргумент, який дозволяє говорити про перспективи токенізованих активів «реального світу» — вартість глобального ринку перевищує сотні трильйонів доларів США незадіяної ліквідності. Станом на 2020 рік лише ринок нерухомості оцінили більш як на $326 трлн. Передбачається, що, принаймні, мала його частина перейде на блокчейн.
На кінець листопада 2023 року, згідно з даними сервісу Rwa.xyz, обсяг заблокованих коштів у протоколах RWA становив $4,5 млрд при $571 млн виданих кредитів. З листопада 2021 року зростання суми кредитів склало понад 6700% з $8,5 млн.
Однак, судячи з прогнозів інституційних компаній, основне зростання ще не відбулося, і ринок має величезні перспективи. Наприклад, аналітики Coinbase вважають, що “бум токенізації” відбудеться в наступні один-два роки. А оцінка зростання всього ринку RWA становить від $5 трлн до $16 трлн до 2030 року, згідно з прогнозами Citigroup та Boston Consulting відповідно.
20 листопада 2023 року команда TypeScript випустила TS 5.3.
Одна з найважливіших змін у TypeScript 5.3 не була згадана у примітках до релізу.
Швидкий приклад коду
// Це було б помилкою у 5.2, але дозволено у 5.3!const array = ["a", "b", "c"] asconst satisfies string[];
const returnWhatIPassIn = <constTextendsany[]>(t: T) => {
return t;
};
// результат - any[] в TS 5.2, но ['a', 'b', 'c'] в 5.3
const result = returnWhatIPassIn(["a", "b", "c"]);
Повне пояснення
Робота з масивами, доступними для читання, TS може іноді доставляти деякі незручності.
Допустимо, ви хочете оголосити масив роутів як const.
Це дозволить вам повторно використовувати роути, оголошені типу.
const arrayOfRoutes = [
{ path: "/home", component: Home },
{ path: "/about", component: About },
] asconst;
// type Route = "/home" | "/about"type Route = (typeof arrayOfRoutes)[number]["path"];
Але якщо ви хочете переконатися, що масив arrayOfRoutes відповідає певному типу?
Для цього можна використовувати satisfies.
const arrayOfRoutes = [
{ path: "/home", component: Home },
{ path: "/about", component: About },
] asconst satisfies {
path: string;
component: React.FC;
}[];
// Тип є "readonly" і не може бути
// присвоєний типу, що змінюється
Єдина проблема полягає в тому, що TypeScript 5.2 це призведе до помилки! Але чому?
Масиви, доступні для читання, та масиви, що змінюються.
Це тому, що arrayOfRoutes доступний лише для читання, а ви не можете використовувати масив, доступний для зміни, для масиву, доступного для читання.
Тому виправлення полягало в тому, щоб зробити тип, який ми покриваємо, масивом readonly:
const arrayOfRoutes = [
{ path: "/home", component: Home },
{ path: "/about", component: About },
] asconst satisfies readonly {
path: string;
component: React.FC;
}[];
// Помилок більше немає!
Тепер, коли ми використовуємо масив, доступний лише читання, TypeScript щасливий.
Параметри типу Const
Те саме відбувається і при використанні параметрів типу const, але ще більш згубно.
У цьому випадку const виводить річ, передану в T, ніби вона була const.
Але якщо ви спробуєте обмежити його масивом, це не спрацює!
Рішення третього рівня (Layer 3 чи L3) — загальна назва протоколів, розгорнутих з урахуванням вже існуючих L2-решень. Як і у випадку з L2, третій рівень мереж призначений для масштабування та розширення функціональних можливостей основного блокчейну.
Концепція подібних розробок існує вже понад вісім років. Одним із перших ідею про багаторівневе масштабування блокчейнів сформулював у 2015 році засновник Ethereum Віталік Бутерін. Найбільшу популярність цієї концепції принесли напрацювання StarWare, опубліковані наприкінці 2021 року.
На листопад 2023 року проектування рішень третього рівня зосереджено навколо блокчейну Ethereum.
<h2Для чого це потрібно?
L2-рішення призначені масштабування базового блокчейну. Вони усувають проблему низької пропускної спроможності та високих комісій за транзакції. У свою чергу, L3 має дві фундаментальні цінності, які важко реалізувати на другому рівні:
масштабованість, що настроюється. Третій рівень блокчейну дає можливість реалізувати індивідуальні налаштування для окремих додатків. Наприклад, відмінні від EVM обчислення чи використання інших форматів даних;
функціонал, що настроюється. L3 дозволить створювати окремі програми та мережі з унікальними точними налаштуваннями. Наприклад, можна реалізувати функцію конфіденційних транзакцій без відображення будь-яких даних на другому рівні.
L3 можна представити як рівень запуску окремих унікальних додатків чи мереж, пов’язаних стандартами L2.
Які L3-рішення існують на ринку?
Ринок L3-рішень лише почав формуватися. Основну розробку ведуть команди L2-рішень на блокчейні Ethereum, включаючи Optimism, Arbitrum та zkSync. Кожна з них має власне бачення.
Hyperchains . Компанія Matter Labs, що стоїть за zkSync, розраховує створити мережу взаємопов’язаних блокчейнів, які використовують доказ із нульовим розголошенням. Щоб реалізувати цей план команда випустила технологічний стек для розробників ZK Stack. Архітектура дозволить розгортати «гіперцепи» як паралельні рішення L2, так і протоколи третього рівня.
У рамках Hyperchains вже запустили тест L3-рішення Pathfinder. На початку 2024 року очікується реліз гібридної криптобіржі GRVT, що поєднує в собі інтерфейс централізованої біржі типу Robinhood та функції некастодіального зберігання активів, як у Uniswap.
Superchain. Це концепт масштабування Ethereum від команди L2-рішення Optimism на основі технологічного стека OP Stack. Поки що ідея команди полягає у створенні паралельних рішень, що діють незалежно один від одного, з подальшою можливістю розробки L3.
У рамках Superchain вже працює накопичувальний клієнт OP Stack від однієї із найбільших венчурних криптокомпаній a16z. У серпні 2023 року американська біржа Coinbase на основі архітектури Optimism запустила мережу другого рівня Base.
Інтерес у перенесенні своїх мереж у Superchain виник і в інших L2. Так, перейти на архітектуру OP Stack запропонувала спільноті компанія cLabs – розробник L2-мережі Celo.
Orbit. Команда Offchain Labs, відповідальна за L2-рішення Arbitrum, у червні 2023 року випустила свій набір інструментів для розробників, націлений на реалізацію L3-рішень на базі Arbitrum. Orbit націлена на створення саме L3-додатків із високим ступенем індивідуальних налаштувань.
Екосистема L3 на Arbitrum вже представлена десятком проектів, що включають Web3 – фінансові ігри, dappsта NFT -проекти.
Які ще рішення називають L3?
L3-рішеннями можуть називатися будь-які мережі та інфраструктурні проекти, побудовані поверх існуючих L1 та/або L2, але не конкурують з ними. Вони виконуватимуть складні обчислення, роботу зі сторонніми даними, пропонуватимуть нові сценарії використання. Наприклад, L3-рішення може принести в блокчейн дані з реального світу, щоб використовувати їх на першому та другому рівнях у DeFi.
До L3 відносять децентралізовану мережу оракулів Chainlink, що відповідає за поставку правильних офчейн-даних для виконання смарт-контрактів на всіх рівнях блокчейну.
До рішень третього рівня також відносять інфраструктурні проекти на кшталт Orbs, що працює як надбудова до різних мереж із чудовим консенсусом та архітектурою. Orbs працює з Ethereum, TON, Polygon, BNB Chain, Avalanche та Fantom, забезпечуючи розробникам та компаніям додатковий функціонал та сумісність для їх додатків.
У цій же області знаходяться рішення від Polkadot, відомі як парачейни. Саме цей функціонал створює додатковий шар для взаємодії Web3-додатків, спрощуючи обмін даними та активами.
Використання Content-Security-Policy разом із React & Emotion
Content-Security-Policy(CSP) – це HTTP заголовок, який покращує безпеку веб-застосунків за рахунок заборони небезпечних дій, таких як завантаження та відправка даних на довільні домени, використання eval, inline-скриптів і т.д. У цій статті буде зроблено фокус на директиві style-srcта її використання разом із CSS-in-JS бібліотекою emotion.
Коротко про CSP та style-src
Content-Security-Policyзаголовок повинен бути виставлений у відповіді разом із завантажуваною веб-сторінкою (наприклад, index.html). Це виглядає так:
Content-Security-Policy: style-src 'self'
style-src– це директива, яка відповідає за те, які стилі можна завантажувати та застосовувати на сторінці. Можливі значення:
'none'– усі стилі заборонені
'self'– Дозволені файли стилів, які завантажуються з того ж домену, що і основний документ (сторінка)
<url>, наприклад https://example.com– дозволені файли стилів з цього домену, також допускаються wildcard (*) на місці під-домену та порту
'<hash-algorithm>-<base64-value>', наприклад 'sha256-ozBpjL6dxO8fsS4u6fwG1dFDACYvpNxYeBA6tzR+FY8='– дозволені файли стилів та inline -стилі (тег <style>), у яких хеш збігається із зазначеним значенням
'nonce-<value>', наприклад 'nonce-abc'– дозволяються inline -стилі, у яких атрибут nonceзбігається із зазначеним (у прикладі – abc)
'unsafe-hashes'– дозволяє inline -стилі, зазначені в атрибуті styleрядком, наприклад <div style="color:red;"></div>, при цьому хеш значення атрибута повинен збігатися з хешом, вказаним у'<hash-algorithm>-<base64-value>'
'unsafe-inline'– дозволяє всі inline -стилі, створені через тег<style>
'unsafe-eval'– дозволяє додати/змінити CSS declarations, які призводять до парсингу рядка, наприклад, за допомогою CSSStyleDeclaration.cssText
Директива може приймати кілька значень через пропуск. У цьому випадку це сприймається як логічне “або” – при задоволенні хоча б одного значення стилі дозволяються.
CSP та emotion
emotionдодає styleелементи динамічно і в останніх версіях не може виймати всі стилі в окремий файл під час складання програми. Це означає, що для того, щоб можна було використовувати emotionразом з style-src, є такі опції:
'unsafe-inline'– Найпростіша опція з усіх. Не вимагає будь-яких налаштувань з боку emotion. При цьому ми знижуємо безпеку нашої програми, тому це рішення можна використовувати лише як тимчасове.
'nonce-<value>'– можна дозволити inline-стилі, створені emotion. Для цього потрібно задати nonceпри створенні cache.
При використанні @emotion/reactабо @emotion/styledце можна зробити так:
Т.к. значення CSP заголовка недоступне коду, що виконується на клієнті, значення потрібно додатково передати іншим чином. Один із варіантів – це створення inline-скрипту зі значенням, яке виставляється на бекенді: