Потужність декораторів TypeScript на живих прикладах. Декорування методів класу.

Декоратори darkside

Декоратори – інструмент декларативного програмування. З їх допомогою можна легко та елегантно додати до класів та членів класу метадані. На основі цих метаданих можна розширювати або змінювати поведінки класів та членів класу, не змінюючи при цьому кодову базу, до якої застосовано декоратор. Саму технологію можна віднести до мета-програмування чи декларативного програмування.

У рамках цієї статті розбирається кілька прикладів із реальних проектів, де застосування декораторів спростило код для розуміння і виключило його дублювання.

Яку проблему вирішують декоратори?

За допомогою декораторів ми можемо уникнути “дублювання” коду, інкапсулюючи наскрізну функціональність в окремий модуль. Забрати зайвий “шум” у коді, що дозволить сфокусуватися автору на бізнес логіці програми.

Наскрізна функціональність – функціональність, яка розподілена по всій кодовій базі. Як правило, ця функціональність не залежить від предметної галузі вашого проекту. До неї можна віднести такі приклади:

  • Логування
  • Кешування
  • Валідація
  • Форматування
  • і т.д.

Для роботи з наскрізною функціональністю існує ціла парадигма -  Аспектно-орієнтоване програмування (AOP). Про її реалізацію в JavaScript раджу прочитати в цій чудовій статті . Так само існують чудові бібліотеки, що реалізують AOP JavaScript:

Якщо Вам цікава тема AOP, раджу поставити ці пакети та погратися з їхньою функціональністю.

У цій статті я спробував показати, як можна вирішити описані вище проблеми, вбудованої в TypeScript функціональністю — декоратори.

Для прочитання цієї статті передбачається, що ви маєте досвід використання react, mobx і typescript, т.к. я не вдаватимуся до подробиць цих технологій.

Трохи про декораторів загалом

У TypeScript декоратором є функція.

Форма застосування: @funcName . Де funcName – ім’я функції, що описує декоратор. Після прикріплення декоратора до члена класу, а потім його виклику, спочатку будуть виконуватися декоратори, а потім код класу. Однак декоратор може перервати потік виконання коду на своєму рівні, так що основний код класу в кінцевому рахунку не буде виконано. Якщо до члена класу прикріплено кілька декораторів, їх виконання відбувається зверху донизу по черзі.

Декоратори досі є експериментальною функцією TypeScript. Тому, для їх використання, вам потрібно додати до вашого tsconfig.json наступне налаштування :

{
  "compilerOptions": {
    "experimentalDecorators": true,
  },
}

Функція-декоратор викликається компілятором, і компілятор сам підставляє потрібні аргументи.

Сигнатура цієї функції для методів класу:

funcName<TCls, TMethod>(target: TCls, key: string, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void

Де:

  • target – об’єкт, для якого буде застосовано декоратор
  • key – ім’я методу класу, що декорується
  • descriptor – дескриптор методу класу.

За допомогою дескриптора ми можемо отримати доступ до вихідного методу об’єкта.

За допомогою шаблонної типізації можна виключити неправильне використання декоратора. Наприклад, ви можете обмежити застосування декоратора методами класу, у яких перший аргумент має строковий тип, визначивши тип дескриптора таким чином:

type TestDescriptor = TypedPropertyDescriptor<(id: string, ...args: any[]) => any>;

У наших прикладах ми використовуватимемо фабрики декораторів . Фабрика декораторів -  це функція яка повертає викликану декоратором під час виконання функцію.

function format(pattern: string) {  
  // это фабрика декораторов и она возвращает функцию-декоратора  
  return function (target) {    
    // это декоратор. Здесь будет код,    
    // который что то делает с target и pattern  
  };
}

Підготовчі роботи

При розгляді прикладів нижче ми будемо використовувати 2 моделі даних:

export type Product = {
  id: number;
  title: string;
};

export type User = {
  id: number;
  firstName: string;
  lastName: string;
  maidenName: string;
}

У всіх функціях декораторів для дескриптора ми будемо використовувати тип PropertyDescriptor , який є еквівалентом TypedPropertyDescriptor<any> .

Додамо функцію-хелпер createDecorator, яка допоможе нам скоротити синтаксичний цукор створення декораторів:

export type CreateDecoratorAction = (self: T, originalMethod: Function, ...args: any[]) => Promise | void;

export function createDecorator(action: CreateDecoratorAction) {
  return (target: T, key: string, descriptor: PropertyDescriptor) => {
    const originalMethod = descriptor.value; // ссылка на оригинальный метод класса
    // переопределяем метод класса
    descriptor.value = async function (...args: any[]) {
      const _this = this as T;
      await action(_this, originalMethod, ...args);
    };
  };
}

Проект побудований на React + TypeScript . Для відображення стану програми на екран використовується чудова бібліотека Mobx . Нижче на прикладах я опущу пов’язані з Mobx частини коду, щоб сфокусувати вашу увагу на проблематики та її вирішення.

 Повну робочу версію коду можна знайти в цьому репозиторії .

Відображення індикатора завантаження даних

Спершу створимо клас AppStore, який міститиме в собі весь стан нашої маленької програми. Додаток складатиметься з двох списків – список користувачів та список товарів. Дані будуть використовуватися з сервісу dummyjson .

AppStore

В результаті рендеру сторінки викликаються два запити на сервер для завантаження списків. AppStore виглядає так:

class AppStore {
  users: User[] = [];
  products: Product[] = [];
  usersLoading = false;
  productsLoading = false;

  async loadUsers() {
    if (this.usersLoading) {
      return;
    }
    try {
      this.setUsersLoading(true);
      const resp = await fetch("https://dummyjson.com/users");
      const data = await resp.json();
      const users = data.users as User[];
      this.users = users;
    } finally {
      this.setUsersLoading(false);
    }
  }

  async loadProducts() {
    if (this.productsLoading) {
      return;
    }
    try {
      this.setProductsLoading(true);
      const resp = await fetch("https://dummyjson.com/products");
      const data = await resp.json();
      const products = data.users as Product[];
      this.products = products;
    } finally {
      this.setProductsLoading(false);
    }
  }
  
  private setUsersLoading(value: boolean) {
    this.usersLoading = value;
  }
 
  private setProductsLoading(value: boolean) {
    this.usersLoading = value;
  }
}

Зміна значень прапорців usersLoading і productsLoading контролює видимість індикаторів завантаження списку. Можна побачити в наведеному коді вище, ця функціональність методу повторюється. Спробуємо скористатися декораторами, щоб усунути це дублювання. Інкапсульуємо всі прапори завантаження в один об’єкт, який лежатиме як loading нашого сховища стану. Для цього визначимо інтерфейс і базовий клас (для повторного використання коду управління станом завантаження прапорів):

type KeyBooleanValue = {
  [key: string]: boolean;
};

export interface ILoadable {
  loading: T;
  setLoading(key: keyof T, value: boolean): void;
}

export abstract class Loadable implements ILoadable {
  loading: T;
  constructor() {
    this.loading = {} as T;
  }
  
  setLoading(key: keyof T, value: boolean) {
    (this.loading as KeyBooleanValue)[key as string] = value;
  }
}

Якщо у вас немає можливості використовувати успадкування, можете використовувати інтерфейс ILoadable і реалізувати власний метод setLoading.

Тепер ізолюємо загальну функціональність контролю стану прапорів декоратор. Для цього створимо узагальнену фабрику створення декораторів loadable, використовуючи функцію хелпер createDecorator :

export const loadable = (keyLoading: keyof T) =>
  createDecorator<ILoadable>(async (self, method, ...args) => {
    try {
      if (self.loading[keyLoading]) return;
      self.setLoading(keyLoading, true);
      return await method.call(self, ...args);
    } finally {
      self.setLoading(keyLoading, false);
    }
  });

Фабрична функція є узагальненою та приймає на вхід ключі властивостей об’єкта, що лежатиме у властивості loading інтерфейсу ILoadable. Для коректного використання цього декоратора потрібно, щоб наш клас реалізував інтерфейс ILoadable. Скористаємося успадкуванням від класу Loadable, який вже реалізує цей інтерфейс і перепишемо код наступним чином:

const defaultLoading = {
  users: false,
  products: false,
};

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];
  constructor() {
    super();
    this.loading = defaultLoading;
  }

  @loadable("users")
  async loadUsers() {
    const resp = await fetch("https://dummyjson.com/users");
    const data = await resp.json();
    const users = data.users as User[];
    this.users = users;
  }

  @loadable("products")
  async loadProducts() {
    const resp = await fetch("https://dummyjson.com/products");
    const data = await resp.json();
    const products = data.users as Product[];
    this.products = products;
  }
}

Як тип для об’єкта як loading ми передаємо динамічно обчислюваний тип typeof defaultLoading від стану за умовчанням цього об’єкта — defaultLoading. Також, привласнюємо цей стан властивості loading. За рахунок цього, рядкові ключі, які ми передаємо в декоратор loadable, контролюється типізацією typescript. Як ви бачите, методи loadUsers та loadProducts краще читаються, а функціональність показу спіннерів інкапсульована в окремий модуль. Фабрика декораторів loadable та інтерфейс ILoadable абстраговані від конкретної реалізації стора і можуть використовуватися в необмеженій кількості сторін у додатку.

Обробка помилок у методі

Якщо раптом, з якоїсь причини, у наведеному вище прикладі сервіс dummyjson перестане бути доступним, наш додаток впаде з помилкою і користувач про це не дізнається. Давайте виправимо цю ситуацію

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];

  constructor() {
    super();
    this.loading = defaultLoading;
  }

  @loadable("users")
  async loadUsers() {
    try {
      const resp = await fetch("https://dummyjson.com/users");
      const data = await resp.json();
      const users = data.users as User[];
      this.users = users;
    } catch (error) {
      notification.error({
        message: "Error",
        description: (error as Error).message,
        placement: "bottomRight",
      });
    }
  }

  @loadable("products")
  async loadProducts() {
    try {
      const resp = await fetch("https://dummyjson.com/products");
      const data = await resp.json();
      const products = data.users as Product[];
      this.products = products;
    } catch (error) {
      notification.error({
        message: "Error",
        description: (error as Error).message,
        placement: "bottomRight",
      });
    }
  }
}

У кожному методі з’являється блок try…catch…, де обробка помилок відбувається в блоці catch. Спливає повідомлення у нижньому правому куті з текстом помилки. Скористайтеся силою декораторів та інкапсулюємо цю обробку в окремий модуль, зробивши її абстрактною:

export const errorHandle = (title?: string, desc?: string) =>
  createDecorator(async (self, method, ...args) => {
    try {
      return await method.call(self, ...args);
    } catch (error) {
      notification.error({
        message: title || "Error",
        description: desc || (error as Error).message,
        placement: "bottomRight",
      });
    }
  });

Фабрична функція приймає на вхід необов’язкові параметри — кастомний заголовок та опис помилок, які будуть виводитися в повідомленні. Якщо параметри не будуть заповнені, буде використовуватися заголовок за промовчанням і повідомлення з поля message помилки. Використовуємо функцію errorHandle у нашому коді:

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];
  
  constructor() {
    super();
    this.loading = defaultLoading;
  }

  @loadable("users")
  @errorHandle()
  async loadUsers() {
    const resp = await fetch("https://dummyjson.com/users");
    const data = await resp.json();
    const users = data.users as User[];
    this.users = users;
  }
  @loadable("products")
  @errorHandle()
  async loadProducts() {
    const resp = await fetch("https://dummyjson.com/products");
    const data = await resp.json();
    const products = data.users as Product[];
    this.products = products;
  }
}

Так просто ми додали функціональність обробки помилок, прибрали дублювання коду і сам код методів залишився простим та читаним.

Повідомлення про успішну роботу методу

Припустимо, що нам потрібно повідомити про успішне завантаження списків користувачів та продуктів. Без декораторів код виглядав би так:

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];
  
  constructor() {
    super();
    this.loading = defaultLoading;
  }

  @loadable("users")
  @errorHandle()
  async loadUsers() {
    const resp = await fetch("https://dummyjson.com/users");
    const data = await resp.json();
    const users = data.users as User[];
    this.users = users;
    notification.success({
      message: "Users uploaded successfully",
      placement: "bottomRight",
    });
  }

  @loadable("products")
  @errorHandle()
  async loadProducts() {
    const resp = await fetch("https://dummyjson.com/products");
    const data = await resp.json();
    const products = data.users as Product[];
    this.products = products;
    notification.success({
      message: "Products uploaded successfully",
      placement: "bottomRight",
    });
  }
}

Також, інкапсулюємо цю функціональність в окремий модуль і зробимо її абстрактною:

export const successfullyNotify = (message: string, description?: string) =>
  createDecorator(async (self, method, ...args) => {
    const result = await method.call(self, ...args);
    notification.success({
      message,
      description,
      placement: "bottomRight",
    });
    return result;
  });

Фабрична функція приймає на вхід обов’язковий параметр повідомлення у повідомленні та не обов’язковий парметр опис повідомлення. Перепишемо код із використанням цієї функції:

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];

  constructor() {
    super();
    this.loading = defaultLoading;
  }
  @loadable("users")
  @errorHandle()
  @successfullyNotify("Users uploaded successfully")
  async loadUsers() {
    const resp = await fetch("https://dummyjson.com/users");
    const data = await resp.json();
    const users = data.users as User[];
    this.users = users;
  }

  @loadable("products")
  @errorHandle()
  @successfullyNotify("Products uploaded successfully")
  async loadProducts() {
    const resp = await fetch("https://dummyjson.com/products");
    const data = await resp.json();
    const products = data.users as Product[];
    this.products = products;
  }
}

Логування методу

Якщо ви збираєте дані вашої програми і потім проводите аналіз для його оптимізації та відстеження помилок, то вам необхідно додавати логи в код. Розглянемо приклад логування – виведення в консоль. Додамо логи до нашого сервісу без використання декоратора:

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];

  constructor() {
    super();
    this.loading = defaultLoading;
  }

  @loadable("users")
  @errorHandle()
  @successfullyNotify("Users uploaded successfully")
  async loadUsers() {
    try {
      console.log(`Before calling the method loadUsers`);
      const resp = await fetch("https://dummyjson.com/users");
      const data = await resp.json();
      const users = data.users as User[];
      this.users = users;
      console.log(`The method loadUsers worked successfully.`);
    } catch (error) {
      console.log(`An exception occurred in the method loadUsers. Exception message: `, (error as Error).message);
      throw error;
    } finally {
      console.log(`The method loadUsers completed`);
    }
  }

  @loadable("products")
  @errorHandle()
  @successfullyNotify("Products uploaded successfully")
  async loadProducts() {
    try {
      console.log(`Before calling the method loadProducts`);
      const resp = await fetch("https://dummyjson.com/products");
      const data = await resp.json();
      const products = data.users as Product[];
      this.products = products;
      console.log(`The method loadProducts worked successfully.`);
    } catch (error) {
      console.log(`An exception occurred in the method loadProducts. Exception message: `, (error as Error).message);
      throw error;
    } finally {
      console.log(`The method loadProducts completed`);
    }
  }
}

Як бачите, код у методах ускладнився для сприйняття та розуміння. І тут ще немає коду, який включає або відключає логи залежно від стейджу збірки або за будь-якими іншими критеріями, що зробить код ще складнішим. Створимо універсальний декоратор для логування.

export type LogPoint = "before" | "after" | "error" | "success";

let defaultLogPoint: LogPoint[] = ["before", "after", "error", "success"];

export function setDefaultLogPoint(logPoints: LogPoint[]) {
  defaultLogPoint = logPoints;
}

export const log = (points = defaultLogPoint) =>
  createDecorator(async (self, method, ...args) => {
    try {
      if (points.includes("before")) {
        console.log(`Before calling the method ${method.name} with args: `, args);
      }

      const result = await method.call(self, ...args);

      if (points.includes("success")) {
        console.log(`The method ${method.name} worked successfully. Return value: ${result}`);
      }

      return result;
    } catch (error) {
      if (points.includes("error")) {
        console.log(
          `An exception occurred in the method ${method.name}. Exception message: `,
          (error as Error).message
        );
      }
      throw error;
    } finally {
      if (points.includes("after")) {
        console.log(`The method ${method.name} completed`);
      }
    }
  });

У цьому декораторі ми визначили точки для логування, які можна налаштовувати, передавши їх типізований масив у перший параметр фабрики декоратора. За промовчанням логується все. Також, функції setDefaultLogPoint можна перевизначити точки логування за промовчанням. Застосуємо цю фабрику в нашому коді:

class AppStore extends Loadable {
  users: User[] = [];
  products: Product[] = [];
  
  constructor() {
    super();
    this.loading = defaultLoading;
  }

  @loadable("users")
  @errorHandle()
  @successfullyNotify("Users uploaded successfully")
  @log()
  async loadUsers() {
    const resp = await fetch("https://dummyjson.com/users");
    const data = await resp.json();
    const users = data.users as User[];
    this.users = users;
  }

  @loadable("products")
  @errorHandle()
  @successfullyNotify("Products uploaded successfully")
  @log()
  async loadProducts() {
    const resp = await fetch("https://dummyjson.com/products");
    const data = await resp.json();
    const products = data.users as Product[];
    this.products = products;
  }
}

Підсумуємо

Області застосування декораторів різноманітні та потенціал їх великий. З їхньою допомогою можна легко вирішити цілий спектр прикладних завдань, зробити код елегантним та простим для читання. Завдяки декораторам, ми можемо інкапсулювати повторюваний наскрізний функціонал у модулі, і перевикористовувати в інших частинах проекту або інших проектах. Однак у невмілих руках такий потужний інструмент може й нашкодити, наприклад, змінити пряме призначення методу. Але, на мій погляд, це не є підставою не вивчати та застосовувати цю технологію у своїх проектах. Декоратори стануть чудовим доповненням у ваших проектах як у ГО так і у функціональній парадигмі.

Переклад статті “The power of TypeScript decorators: real cases. Decorating class methods.

Що таке веб-стандарти та як працює веб-браузер?

як працює веб-браузер

Розповім я вам одну історію. Якось я розробляв черговий компонент з вибором дати для нашої системи проектування. Компонент складається з поля для текстового введення та спливаючого календаря, що відображається при натисканні мишею по цьому полю. Потім календар, що випливає, можна закрити, клацнувши поза ним або якщо вибрано дату.

Компонент для вибору дати

У більшості реалізацій такої логіки клацнути поза календарем застосовувалися конкретні слухачі подій, прикріплені до DOM. Але я хотів зробити наш компонент з вибором дати доступним так, щоб можна було відкривати календар із вкладками і таким же чином його закривати. Крім того, слухачі подій можуть вступати в конфлікт один з одним, якщо ви розмістите на сторінці кілька таких компонентів для вибору дати.

А якщо просто покластися на нативні події фокусування і розмиття, а не зв’язуватися з клацаннями поза полем? У такому разі, природно, підтримуються вкладки, події торкання та клацання, і все це вже реалізовано у браузері. Єдина проблема, яку доведеться вирішити в даному випадку, така: що робити, якщо ви клацаєте спливаючим полем, але без вибору дати. Тоді фокус переміщається на календар, в результаті з полем для введення відбувається розмиття, і зрештою спливаюче поле ховається.

Тут я задумався, а чи є спосіб зробити клацання, але не зрушувати фокус. Нашвидкуруч погуглив, я знайшов, як це зробити: придушити подію mouseDown, за замовчуванням спрацьовує для спливаючого поля. Все так: одного рядка вистачило, щоб усі клацання працювали, але поле для введення тексту залишалося у фокусі.

Здавалося: і все, рішення знайдено. Давайте рухатись далі. Але щось мене зупиняло. Чому саме mouseDown, а не mouseUp зупиняє фокус, але пропускає клацання? Це частина якогось чинного стандарту? Чи працюватиме це в кросбраузерному режимі? Бібліотека React Testing, за допомогою якої у нас робилися інтеграційні тести, також не підтримувала цієї можливості, тому мені довелося б змінювати функцію симуляції.

Що таке веб-стандарт?

Добре, оскільки відповіді зі Stack Overflow мені було недостатньо, то де ще шукати інформацію про поведінку веб-браузерів, як не в стандартних стандартах?
Ймовірно, ви чули про W3C , він же – Консорціум Всесвітньої Павутини. Це міжнародне співтовариство, яке розробляє відкриті стандарти для Інтернету. W3C прагне гарантувати, що всі керуються одними і тими самими нормами, і нам не доведеться підтримувати десятки різних оточень. Якщо зайдете на їхній сайт, то знайдете список усіх стандартів , над якими вони працюють.

Давайте заглянемо в один документ, де можуть бути відповіді на наші запитання –  UI Events Standard(Стандарт подій інтерфейсу користувача). У цьому документі вказано потік подій DOM, визначено список подій та порядок їх виконання. Якщо вам здавалося, що веб-стандарти – це нудні, каламутні простирадла тексту, через які доводиться продиратися – одразу переходьте до розділу DOM Event Architecture (Архітектура подій DOM). Тут пояснено сплив подій, розказано про захоплення подій, а сам текст забезпечений веселими картинками. Проте це дуже конкретний документ, саме такий, яким і має бути стандарт. Вас здивує його якість, він насправді дуже якісно написаний, рясніє прикладами та рекомендаціями.

Також в ньому визначено і нашу подію mouseDown, зокрема, як вона діє за умовчанням:

“У багатьох реалізаціях подія мусила вживатися, щоб ініціювати ряд контекстно-залежних дій, що виконуються за умовчанням. Такі дії можна запобігти, якщо ця подія скасована. До таких дій, що виконуються за замовчуванням, можуть входити: початок перетягування, де об’єкт, що перетягується, – це зображення або посилання; початок виділення тексту, т. д. Крім того, в деяких реалізаціях передбачається можливість панорамування за допомогою миші, що активується, коли середня кнопка миші втоплена в момент диспетчеризації події, що минув.

Добре, для нашої події передбачені деякі дії за умовчанням, але в самій події фокусу немає нічого специфічного, оскільки фокус дійсно залежить від браузера. Давайте познайомимося з ними.

Знайомство з браузерними двигунами

Сучасний браузер – це дуже складний зразок софту з базою коду приблизно десятки мільйонів рядків . Тому браузер зазвичай ділиться на кілька частин.
Щоб визначити, де саме визначаються події фокусу, потрібно зробити огляд, який би дозволив зрозуміти, за що відповідає кожна частина. Почнемо з Chromium і документації щодо його проектування Getting Around The Chrome Source Code . Як бачите, тут безліч модулів і логіка, за яку відповідають модулі, у всіх модулів різна.

Узагальнений огляд Chromium

Давайте коротко розберемо їх усе, щоб зрозуміти, як взаємодіють усі ці компоненти.

  • chrome: це базова програма з логікою запуску, інтерфейсом користувача і всіма вікнами. Він містить проекти для chrome.exe та chrome.dll. Тут ви також знайдете ресурси, наприклад, іконки чи курсори.
  • content: це серверна частина програми, що обробляє комунікацію з дочірніми процесами.
  • net: це мережна бібліотека, яка допомагає виконувати запити на веб-сайти.
  • base: це місце для загального коду, що поділяється між усіма субпроектами. Сюди можуть бути включені такі речі, як операції над рядками, узагальнені утиліти, тощо.
  • blink: це двигун рендерингу, що відповідає за весь конвеєр відображення, у тому числі, за дерева DOM, стилі, події, інтеграцію з V8.
  • v8: остання більшість браузерного движка на JavaScript. Завдання цього компонента – компілювати JavaScript у нативний машинний код.

Як бачите, браузер складається з кількох незалежних частин, які спілкуються один з одним через API. З погляду розробника зазвичай найцікавіші Blink та V8. Дії за замовчуванням, що визначаються браузером, не входять до складу V8, але у Blink всі вони мають бути визначені та реалізовані. Але перш ніж переходити до бази коду Blink, давайте розберемося, як веб-браузери працюють з точки зору користувача.

Конвеєр візуалізації

Уявіть, що ви вводите в браузер адресу домену, а потім браузер вибирає та завантажує набір ресурсів: HTML, CSS, файли JS, зображення, ярлики. Але що має статися далі?

Браузерний конвеєр рендерингу

На першому кроці HTML-файли проходитимуть синтаксичний розбір і перетворюватимуться на дерево DOM . DOM – це не тільки внутрішнє уявлення сторінки, але й API, що відкривається JavaScript для виконання запитів і модифікації рендерингу за допомогою системи так званих «прив’язок».

Наступний крок після побудови дерева DOM – це обробка стилів CSS. Для цієї мети в браузерах є інструмент аналізу CSS, що збирає модель стильових правил. Побудувавши модель стильових правил, можна об’єднати їх із набором стилів, що задаються за замовчуванням (що надаються браузером) і обчислити остаточне значення кожної стильової властивості для кожного елемента DOM. Цей процес називається роздільною здатністю (або перерахунком) стилів .

У наступній частині описує макет , потрібно визначити візуальну геометрію для всіх елементів. На даному етапі кожен елемент отримує координати (x та y), ширину та висоту. Двигун компонування обчислює всі зони виходу за межі та веде їх облік – яка частина елемента буде видимою, а яка ні.

Коли у нас будуть всі координати для всіх елементів, настане час для відображення . Для цієї операції ми скористаємося координатами з попереднього кроку та кольорами із стильових правил, після чого скомбінуємо на їх основі список інструкцій з малювання. Важливо малювати елементи у правильному порядку, щоб, накладаючись одне одного, вони зберігали правильну «поверховість». Цей порядок можна змінити за допомогою стильового правила z-index.

Давайте виконаємо наші інструкції для відображення за списком і перетворимо їх на растр колірних значень. Цей етап називається розтеризацією . Саме зараз ми візьмемо наші зображення та декодуємо їх у карту бітів.

Пізніше розтеризована карта бітів зберігатиметься у пам’яті GPU. На даному етапі в роботу включаються бібліотеки, що абстрагують апаратне забезпечення та виклики до OpenGL і DirectX під Windows. Коли GPU отримує команди для відображення растрового малюнку, на дисплеї відображаються відповідні пікселі.

Ось у нас є всі найважливіші частини рендерингового конвеєра. Але що станеться, якщо прокрутити сторінку або застосувати на ній деяку анімацію? Насправді рендеринг не статичний. Для представлення змін використовуються анімаційні кадри. Кожен кадр повністю відображає стан контенту у конкретний час. Найскладніше при організації цього процесу – досягти потрібної продуктивності. Щоб анімація йшла гладко, необхідно генерувати щонайменше 60 кадрів на секунду. Провернути весь конвеєр 60 разів на секунду буде практично неможливо, особливо на повільних пристроях.

А що, якщо ми не будемо щоразу рендерити все заново, а надамо спосіб інвалідувати елемент на конкретному етапі. Скажімо, якщо ви динамічно змінюєте колір кнопки, то браузер помітить її вузол як інвалідний, і в наступному кадрі анімації її буде відображено заново. Якщо нічого не зміниться, ми зможемо повторно використовувати старий кадр.

Цей спосіб зручний для оптимізації невеликих динамічних змін вмісту. Давайте подумаємо, що робити, якщо потрібно змінювати великі області. Наприклад, після повного прокручування сторінки всі пікселі на новому екрані будуть іншими. Тому сторінка декомпонується на рівні, кожен із яких раструється незалежно. Рівень може бути дуже малий і представляти лише один вузол a DOM. Потім всі ці рівні комбінуватимуться в іншому потоці, який називається потік композитора . За такої оптимізації не доводиться повторно раструвати все відразу, достатньо виконувати ці операції для невеликих шарів, а потім правильно компонувати їх один з одним.

Отже, ми коротко розглянули, що робить Blink і як виглядає конвеєр рендерингу. Давайте заглибимося в код.

Навігація по базі коду Blink

Здається, ми наближаємося до фінішної прямої. Давайте відкриємо репозиторій Blink і розглянемо його.

Кореневий каталог репозиторію Blink

Швидко стає зрозуміло, що, хоча ми змогли значно конкретизувати наше вихідне питання, масив коду поки що все одно занадто великий, щоб вручну знайти в ньому конкретний рядок, що відповідає за запобігання фокусу.

Давайте спробуємо пошукати в Google на ім’я події:

mousedown site:https://chromium.googlesource.com/chromium/blink/+/master/Source

Приводить нас до файлу EventHandler , де можна знайти деталі реалізації для багатьох вхідних подій. У тому числі, той рядок, який нас найбільше цікавить.

bool swallowEvent = !dispatchMouseEvent(EventTypeNames::mousedown, mev.innerNode(), m_clickCount, mouseEvent);

Значення , що повертається dispatchMouseEventозначає «продовжувати обробляти так, як задано за замовчуванням», тому, у разі використання preventDefaultswallowEventдорівнює true.

Трохи нижче розташований виклик події фокусування, який спрацьовує лише у випадку, якщо swallowEvent == false.

swallowEvent = swallowEvent || handleMouseFocus(MouseEventWithHitTestResults(mouseEvent, hitTestResult), sourceCapabilities);

Можете дослідити не тільки подію фокусування, але і всі дії, які задаються за умовчанням для події mouse down, у тому числі виділення, перетягування і роботу з повзунком. Також тут реалізуються подія відпускання кнопки миші та подія подвійного клацання.

Gecko і WebKit

Діставшись сюди, ми вже встигли чимало покопатися у вихідному коді браузерів і дуже непогано уявляємо собі їхню структуру. Так чому б не перевірити Firefox чи взагалі Safari. Двигун браузера Firefox називається Gecko, а двигун Safari – WebKit.

У Gecko також передбачена оглядова сторінка для розробників, тому легко усвідомити основні концепції цього движка. Спираючись на досвід роботи з Chrome, ви знайдете тут акуратний файл EventStateManager на 6000 рядків коду, в якому для подій задаються дії та стандартні поведінки.

WebKit– Це браузерний двигун від Apple, що використовується в Safari та інших продуктах Apple. Blink від Chrome є форком WebKit, тому у них багато спільного, і мені не важко знайти реалізації подій в їх версії файлу EventHandler .

Тепер, переконавшись, що можна безпечно придушити подію, що мусила встановити, я можу повернутися до початку цієї посади і спокійно доопрацювати інструмент для вибору дати.

Заключення

Разом ми пройшли шлях після простої проблеми до введення в веб-стандарти і розбору деталей, що стосуються реалізації браузерів.

Нехай вас не лякає прихована складність наявних модулів, що навіть стосуються браузера або компілятора. На Вас чекає захоплююча подорож. Ймовірно, ви легко знайдете, що тут можна було б покращити, і, що набагато важливіше, на власному досвіді набудете унікальних знань про те, як саме влаштовано. Особисто я перелопатив гору документації, працюючи над цим дослідженням, і рекомендую всім діяти так само. У всіх браузерах надається чудова документація, тому я справді не впевнений, а чи потрібно для роботи ще щось, крім неї.

Переклад статті “What are Web Standards and how does Web Browser work?

Що таке шорт і лонг та як їх використовувати у торгівлі криптовалютами?

Що таке шорт і лонг

Головне

  • Шорти та лонги – короткі та довгі позиції (на продаж та на покупку) у трейдингу . Застосовуються головним чином під час маржинальної торгівлі, тобто торгівлі з плечем.
  • Більшість криптовалютних активів характерна висока волатильність. Використання шортів та лонгів дає можливість трейдерам отримувати прибуток у процесі цінових коливань.
  • При торгівлі в довгих і коротких позицій слід пам’ятати про хеджування — певні дії, спрямовані на захист від ситуацій, коли ринок рухається у протилежному напрямку відкритим позиціям.

Походження шортів та лонгів

У середньовічній Європі для обліку боргів використовувалися ціпки-бирки або лічильні палиці, що виготовляються з ліщини. На одній із граней бірки поперечними насічками позначали суму, що пускається в оборот, після чого розбирали бірку вздовж через насічки, але не повністю, а з відрубом в районі «рукоятки». В результаті виходила довга частина з рукояткою (stock) та коротка частина (foil), що доповнює цю довгу частину до повної палички. Насічки були на обох частинах. За збігом цих частин проводився контроль. Вважалося, що через фактуру ліщини підробка була неможлива. Дві частини зберігали дві сторони-учасниці транзакції. Із цієї практики, ймовірно, виникли терміни «фондовий ринок» (stock market), а також «лонг» (long) і «шорт» (short).

Висловлювання «короткі» і «довгі» позиції набули поширення на американських фондових і товарно-сировинних біржах 1850-ті роки. Можливо, найраніша згадка про короткі і довгі позиції присутня в журналі The Merchant’s Magazine, and Commercial Review, Vol. XXVI, за січень-червень 1852 року.

Незважаючи на назви, період для короткої позиції може бути досить довгим (тиждень, місяць), а період для лонга досить коротким. Зі світу традиційних фінансів терміни шорт і лонг перекочували в біткоін-індустрію.

Хто такі «бики» та «ведмеді»

Біржових гравців прийнято називати биками чи ведмедями залежно від того, якої стратегії вони йдуть. Тих, хто грає на підвищення (тобто тих, хто відкриває лонги), називають «биками», а гравців, які розміщують короткі позиції, тобто ставлять на зниження ринку — «ведмедями».

Ці терміни умовні: на крипторинці немає чистих бугаїв і ведмедів, часто один і той же трейдер одночасно торгує і в шорт, і в лонг, хоча обсяги позицій можуть відрізнятися.

Що таке лонг

Лонг (довга позиція) – купівля активу в очікуванні того, що він зросте в ціні. Розмір прибутку залежить від збільшення вартості активу. Довга позиція є найбільш популярним типом угоди у роздрібних інвесторів та використовуються на спотовому ринку.

Що таке шорт

Шорт простими словами – продаж фінансового інструменту в очікуванні, що він подешевшає.

Однак механіка короткої позиції дещо складніша за лонг. У рамках цієї схеми трейдер бере актив у борг і продає його на відкритому ринку за поточною ціною. Потім він чекає падіння курсу, купує ту кількість активу, яку брав у борг, за нижчим курсом і віддає борг із відсотками. Отриманий завдяки змінам цін прибуток трейдер залишає собі. У протилежному випадку, якщо ціна активу підвищиться, інвестор отримає збитки.

Приклад

У грудні 2017 року трейдер придбав біткоїни по $19 000 за монету. Він продав ці монети в той же період за $19 000, а потім виплатив позикодавцю приблизно $6000 за кожен BTC, коли в лютому 2018 року ціна значно знизилася . З кожної монети він отримав прибуток у $13 000.

До чого тут маржинальна торгівля

Якщо на спотовому ринку криптовалют можна, по суті, торгувати лише довгими позиціями (хоча і є прийоми, за логікою схожі на шортинг), то в маржинальній торгівлі стає можливим повноцінне відкриття коротких.

У рамках маржинальної торгівлі, яка дає можливість здійснювати угоди з плечем, користувач повинен надати заставу — внести на депозит суму (маржу), яка гарантує виплату боргових зобов’язань за встановленими біржею правилами.

Концепція маржі тісно пов’язана з концепцією левериджу або кредитного плеча (leverage) – множника, який збільшує доступний для операції депозит користувача за рахунок позикових коштів. На ринку криптовалют цей коефіцієнт може коливатися від 2:1 до 100:1 та більше. Тобто торгівля із плечем 50x означає, що при внесенні на депозит готівки на суму $100 ви можете відкрити позиції на суму до $5000.

Якщо ринкова вартість криптовалюти рухається в бік, що очікується трейдером, дохід збільшується пропорційно обраному кредитному плечу. У момент закриття такої позиції тіло застави повертається кредитору разом із комісійними зборами, а залишок отриманого прибутку зараховується на рахунок користувача.

Якщо ж ціна рухається у протилежному напрямку, то, як тільки вартість активів (і власних, і позикових) трейдера досягає розміру кредиту з відсотками (суми, яку трейдер має повернути кредитору), біржа автоматично ліквідує всі позиції гравця та повертає кредитору його кошти. У суму, яку кредитор повертає, повністю входить маржа.

У класичній торгівлі із плечем на фондовому ринку ліквідації позиції передує так званий маржин-кол — вимога додаткового забезпечення. Часто маржин-коллом називають безпосередньо момент ліквідації, на сленгу криптотрейдерів – “зловити моржа”.

Невдалу угоду трейдер може завершити самостійно, не чекаючи на ліквідацію. При цьому він втрачає не всю позицію, а лише частину маржі. Самостійно ліквідувати позицію можна вручну і за допомогою “стоп-лосса” (Stop Loss) – ордера для обмеження торгових ризиків, що передбачає автоматичне закриття угоди при досягненні певної цінової позначки.

Що таке хеджування?

На криптовалютному ринку застосовується механізм, відомий як хеджування — страхування на випадок, якщо тренд руху ціни активу не відповідає позиції трейдера. Наприклад, у вас відкрита довга позиція, а ціна криптовалюти знижується.

В основі хеджування – відкриття шорт-позицій, які врівноважують лонг-позиції та дозволяють залишитися «в нулі» при небажаній зміні ринкової ситуації. Інвестор залишає початкову лонг-позицію незайманою і відкриває шорт або ж використовує додаткові можливості.

Хеджування позиції — рішення для середньо- та довгострокової стратегії. Цей механізм дещо суперечить внутрішньоденному трейдингу, де переважають ринкові спекуляції. Популярний спосіб хеджування позицій передбачає використання ф’ючерсних контрактів: безстрокових та термінових.

Безстрокові контракти працюють за таким принципом: щовісім годин встановлюється так звана ставка фінансування. Останню учасники угод платять один одному замість того, щоб переказувати самі контракти або їх повні вартості. Залежно від ринкової ситуації платять ставку або власники лонг-контрактів власникам шортів, або навпаки.

Термінові контракти виконуються автоматично, якщо інвестор не закриває їх до дня експірації. Хеджуватися можна не лише ф’ючерсами, а й опціонами — похідними фінансовими інструментами для більш розвинених учасників ринку.

Що таке усереднення?

У рамках цієї стратегії інвестор скуповує актив за дедалі нижчою ціною, знижуючи тим самим середню вартість покупки.

Приклад

Ціна біткоїну досягла $29000, потім почала знижуватися. Побачивши фазу корекції, трейдер став купувати монети на послідовних рівнях зниження: $28000, $26000, $24000, $22000, $20000. Середня ціна покупки склала $24000. Після фази корекції курс почав зростати і згодом повернувся до рівня $29000 доларів.

Плюси та мінуси лонгів та шортів

Відкриття довгих позицій — зрозуміліша новачкові стратегія, яка зводиться до простого принципу «купуй дешевше, продавай дорожче».

Шортинг може бути ефективною інвестиційною стратегією, але набагато ризикованіший, ніж вкладення на тривалий термін або усереднення. Відкривати короткі позиції великі суми варто лише досвідченим трейдерам, здатним комплексно аналізувати ринкову динаміку.

Спробувати себе у криптотрейдінгу можна на криптобіржі Binance. На цей час це сама велика криптобіржа с великим вибором інструментів для торгівлі.

Всім профіту та будьте обережні при інвестуванні в крипту.

Крапка з комою в JavaScript: Чи дійсно вона вам потрібна?

JavaScript

У JavaScript крапки з комою є необов’язковими.

// Both statements work the same way
console.log("Hello")
console.log("Hello");

Однак є ситуації, в яких пропуск крапки з комою може призвести до небажаних наслідків. Тому немає однозначної відповіді на питання, чи слід використовувати крапку з комою, чи ні.

Використання крапок з комою завжди викликає суперечки у спільноті JavaScript. Існують вагомі аргументи на користь використання крапки з комою. Але також є вагомі причини, чому їх не слід застосовувати.

Це вичерпний посібник з використання крапок з комою в JavaScript.

Спочатку ми розглянемо правила використання крапок з комою у коді JavaScript. Потім ви дізнаєтеся, як працює автоматична вставка крапки з комою за кадром. І останнє, але не менш важливе: ви побачите список плюсів та мінусів використання крапок із комою.

Наприкінці цього посібника ви зможете вирішити, чи хочете ви використовувати крапки з комою чи ні.

Посібник з використання крапок з комою в JavaScript

Перш ніж обговорювати плюси та мінуси використання крапок із комою, необхідно зрозуміти, як вони взагалі використовуються.

Необхідне використання: Розділити два оператори в одному рядку

Якщо у вас є два оператори JavaScript в одному рядку, ви повинні поділити їх крапкою з комою. Можливо, найпоширенішим прикладом цього є цикл for.

Наприклад:

const numbers = [1, 2, 3, 4, 5];

for (let i = 0; i < numbers.length; i++) {
    console.log(numbers[i]);
}

На виході:

1
2
3
4
5

Цикл for не буде працювати без крапки з комою, якщо його умова задається в одному рядку.

Необов’язкове використання: Крапки з комою як роздільники операторів

Крім поділу операторів для одного рядка, JavaScript немає інших обов’язкових випадків використання крапки з комою. Однак, за бажанням, ви можете використовувати крапки з комою для завершення оператора, навіть якщо були розриви рядків.

Ось кілька поширених прикладів операторів, які можна завершити крапкою з комою:

let i;                        // variable declaration
i = 5;                        // value assignment
let x = 9;                    // declaration and assignment
var fun = function() {...};   // function definition
alert("hi");                  // function call

Пам’ятайте, що всі вищезгадані крапки з комою необов’язкові. Код працюватиме і без них.

Уникайте крапок із комою

Є ситуації, у яких слід уникати використання крапок із комою.

Уникайте крапок з комою після '}'. Не ставте крапку з комою після закриття фігурної дужки }.

Єдиним винятком є ​​оператор присвоєння, наприклад, такий:

var data = {name: "Alice", age: 25};

У цьому випадку можна використовувати крапку з комою.

Ось кілька прикладів того, як не використовувати крапку з комою після фігурної дужки, що закриває:

if  (...) {...} else {...}
for (...) {...}
while (...) {...}
function (arg) { /* actions */ }

Уникайте крапок з комою після ) в операторах if, for, while або switch.

У попередньому розділі ви дізналися, що не слід використовувати крапку з комою після закриття фігурної дужки. Однак якщо ви випадково це зробите, це буде просто проігноровано.

Жодної шкоди від цього не станеться. Але якщо ви поставите крапку з комою там, де її не повинно бути, то виникнуть проблеми.

Не додавайте крапку з комою після закриває дужки ) в:

  • операторів If
  • циклах For
  • циклах While
  • операторах Switch

Давайте розглянемо приклад того, чому важливо пам’ятати.

Якщо ви напишете оператор if наступним чином:

if (0 > 1); { console.log("Hello") }

Це еквівалентно наступному:

if (0 < 1);

console.log("Hello")

У цьому випадку він виводить повідомлення на консоль, хоча явно не повинен цього робити. Це тому, що крапка з комою повністю завершує оператор if. Потім блок коду, що йде за оператором if, виконується як окремий. Тому будьте обережні та не зловживайте крапкою з комою!

Винятки при використанні крапки з комою

Раніше у цій статті ви бачили приклад циклу for з крапкою з комою. Це винятковий випадок використання крапки з комою.

Погляньте на цей простий цикл:

for (let i = 0; i < 10 ; i++) { } // Works

Як ви можете бачити, крапка з комою не ставиться відразу після i++ .

Насправді після третього оператора в циклі for не можна ставити крапку з комою.

Якщо ви так зробите, то виникне синтаксична помилка:

for (let i = 0; i < 10 ; i++;) { } // SyntaxError

Це все, що вам потрібно знати, коли йдеться про правила використання крапок з комою в JavaScript.

Тепер давайте коротко обговоримо, чому використання крапок з комою в JavaScript необов’язкове.

Автоматична вставка крапки з комою у JavaScript

JavaScript не вимагає крапок з комою (за винятком одного виключення, яке ви бачили раніше).

Це відбувається тому, що JavaScript досить розумний і може автоматично додавати крапки з комою там, де потрібно.

Все відбувається “за лаштунками”, і ви нічого не помітите.

Такий процес називається автоматичною вставкою крапок з комою ( Automatic Semicolon Insertion . ASI).

Правила ASI у JavaScript

Парсер JavaScript додає крапку з комою в будь-якому з наступних випадків:

  1. Наступний рядок коду починається з коду, який явно перериває поточний рядок коду.
  2. Коли наступний рядок коду починається з }.
  3. Досягши кінця файлу.
  4. Якщо будь-який з наступних операторів зустрічається у вигляді окремого рядка
  • return (повернення)
  • break (перервати)
  • throw (викинути [помилку])
  • continue (продовжити)

Важливо розуміти, що ASI не завжди є коректним на 100%.

Крапка з комою використовується для поділу операторів на JavaScript, а не для їх завершення.

Це те, що ASI намагається зробити вас.

Якщо говорити простіше, то правила ASI можна сформулювати так:

Не кожне перенесення рядка вимагає крапки з комою. Однак розрив рядка, який не піддається парсингу без крапки з комою, потребує її використання.

Наприклад:

let x
x
=
10
console.log(x)

Цей фрагмент коду інтерпретується ASI як:

let x;
x = 10;
console.log(x);

В даному випадку ASI проробив відмінну роботу, зумівши розібратися в тому, як триває код між рядками 2-4.

Однак, іноді він може не знати, чого ми намагаємося досягти.

Наприклад, цей рядок коду призводить до помилки

const s = 'World'
const ab = "Hello" + s

[3].forEach(n => console.log(n))

В результаті виникає така помилка:

Uncaught TypeError: s[3].forEach is not a function
    at :4:5

За описом помилки ви можете здогадатися, чому це відбувається.

Причина, через яку цей валідний фрагмент коду не працює, полягає в тому, що ASI не вставляє крапку з комою після другого рядка.

Натомість, він інтерпретує рядки 2 і 4 як продовження одного і того ж оператора наступним чином (відповідно до правила ASI номер 1):

const s = 'World';
const ab = "Hello" + s[3].forEach(n => console.log(n));

ASI думає, що s – це масив, і ви намагаєтеся отримати доступ до його 4 елемента за допомогою s [3] .

Але це не те, що ви плануєте здійснити.

Щоб змусити цей рядок працювати так, як очікується, необхідно вручну додати крапку з комою наприкінці другого рядка:

const s = 'World'
const ab = "Hello" + s;

[3].forEach(n => console.log(n)) // Prints '3'

Тепер код працює так, як передбачалося.

Інший приклад, коли ASI може викликати проблеми, – це оператори return.

Наприклад:

function getData() {
  return
  {
    name: 'Bob'
  }
}

console.log(getData())

На виході:

undefined

В результаті виводиться undefined, хоча малося на увазі, що з’явиться ім’я {name: 'Bob'}.

Так відбувається тому, що 4 правило ASI говорить, якщо оператор return зустрівся у вигляді окремого рядка, то ставиться крапка з комою.

Тому ASI бачить наведений вище код так:

function getData() {
  return;
  {
    name: 'Bob'
  }
}

console.log(getData())

Іншими словами, функція getData() нічого не повертає, а потім випадково створює об’єкт, з яким нічого не робить.

Таким чином, в консолі ви бачите undefined.

Для виправлення цього потрібно додати фігурну дужку, що відкриває, в той же рядок, що і оператор return:

function getData() {
  return {
    name: 'Bob'
  }
}

console.log(getData())

На виході:

{
  name: "Bob"
}

Тепер цей фрагмент коду працює як належить.

Завдяки цьому розділу ви навчилися бути обережними з ASI. Незважаючи на те, що в більшості випадків він діє правильно, іноді його інтерпретація ваших намірів може виявитися неправильною.

Далі перейдемо до найцікавішого, тобто до причин, з яких ви повинні або не повинні використовувати крапку з комою JavaScript.

Чому ви повинні використовувати крапку з комою: 5 причин

Використання або невикористання крапок із комою викликає спекотні суперечки серед веб-розробників.

Ось 5 причин, з яких ви повинні використовувати крапку з комою у своєму коді.

1. Іноді обов’язкова

Як було сказано раніше у цій статті, іноді необхідно використовувати крапку з комою.

Наприклад, якщо ви пишете цикл for, необхідно використовувати крапку з комою при вказівці параметра циклу та умов. В іншому випадку цикл не працюватиме.

Крім того, ASI (автоматична вставка крапки з комою) JavaScript не завжди відповідний на 100%.

Іноді він може неправильно витлумачити ваші наміри і не додати крапку з комою там, де вона потрібна. Це може призвести до появи непередбачених помилок у коді.

Один із таких прикладів ви також бачили раніше у цьому посібнику.

2. Ви звикли використовувати крапки з комою

Можливо, у процесі роботи ви звикли до використання крапок із комою в коді.

У деяких інших мовах широко використовуються крапки з комою, тому звикання до них є звичайною справою. Якщо ваш мозок не сприймає код JavaScript без крапок із комою, навіщо від них відмовлятися?

Не соромтеся використовувати крапки з комою, щоб зробити код більш зрозумілим для вас, якщо це те, що ви звикли робити.

3. Явно вказує на закінчення оператора

Крапка з комою – це простий спосіб чітко позначити закінчення оператора. При використанні крапки з комою немає місця для плутанини. Рядок коду закінчується на крапці з комою.

4. Менше приводів для занепокоєння

Якщо ви завжди використовуєте крапку з комою, то не варто переживати з приводу ASI. Це дає вам менше приводів для занепокоєння.

Після кожного рядка коду вам не потрібно замислюватися про те, чи зіпсує відсутність крапки з комою ситуацію чи ні. Однак ASI все одно може зіпсувати все, як ви бачили на прикладі оператора return .

5. ASI може бути змінено

Правила ASI можуть змінитися у майбутньому. Хоча це малоймовірно, але таке можливо. Таким чином, покладатися на правила ASI про вставку крапок з комою завжди за одним і тим же принципом надійно не на 100%.

Якщо ви пишете код з урахуванням поточного ASI, можете зіткнутися з деякими проблемами, якщо правила поміняються. Але майте на увазі, що у 99,9% випадків ASI виконує свою роботу коректно. Більше того, правила навряд чи зміняться найближчим часом.

Тепер, коли ви побачили безліч причин для використання крапок з комою, поговоримо про те, чому їх не слід використовувати.

Чому не слід використовувати крапки з комою: 3 причини

Зверніть увагу, що якщо ви чуєте, як хтось каже: “Ніколи не слід використовувати крапку з комою”, він помиляється. Це тому, що крапка з комою в окремих випадках обов’язкова.

У будь-якому випадку, поговоримо про мінуси крапок з комою, перерахувавши 3 причини, з яких їх не слід використовувати.

1. Крапки з комою вставляються автоматично

Як ви вже з’ясували, крапки з комою вставляються ASI. Таким чином, вам не потрібно писати те, що все одно буде проігноровано.

2. Менше написаного коду та менше шуму

Кожен символ коду впливає на читабельність та якість коду. Якщо ви не використовуєте крапки з комою, то цим заощадите символи в кожному написаному рядку коду.

3. Кілька операторів в одному рядку – погана практика

Використання крапок із комою дозволяє записувати по кілька операторів на одному рядку. Але це погана практика.

Ви ніколи не повинні писати оператори в одному рядку (якщо це не потрібно).

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

Так використовувати крапки з комою чи ні?

Під час вивчення цієї статті ми обговорили причини, через які варто і не слід використовувати крапку з комою.

Як показує практика, є набагато більше причин для використання капок з комою, ніж для її відсутності. Таким чином, я рекомендую використовувати крапки з комою. Проте рішення лишається за вами.

Якщо ви працюєте в команді розробників програмного забезпечення, ви повинні дотримуватися загальноприйнятих правил. Якщо команда використовує крапки з комою, то і ви повинні їх застосовувати. Якщо команда цього не робить, то вам не слід їх ставити.

Також пам’ятайте, що потрібно бути послідовним у застосуванні крапок з комою та без них.

Якщо ви пропустили одну крапку з комою, то пропускайте їх усі (крім обов’язкових). Ви також можете налаштувати свій лінтер на автоматичне видалення крапок з комою.

Висновок

Сьогодні ви дізналися про використання крапок з комою у JavaScript. Нагадаємо, що крапки з комою не є обов’язковими у JavaScript. Водночас процес автоматичної вставки крапки з комою (ASI) додає крапки з комою там, де це необхідно.

Однак ASI не є правильним у 100% випадків. Крім того, у деяких ситуаціях ви просто зобов’язані використовувати крапку з комою. Інакше код не працюватиме.

Використовувати крапки з комою чи ні – повністю залежить від вас. На мій погляд, користі від використання крапок з комою більше, ніж від їхньої відсутності. Але ваша думка може відрізнятись.

Переклад статті “JavaScript Semicolon: Do You Really Need It? (Updated 2022)

React, Vue, Angular. Що краще?

React, Vue, Angular. Що краще?

Під час розвитку веб-розробки 3 JavaScript-фреймворку стали добре відомі всім front-end розробникам: React, Vue.js та Angular.

React вважається бібліотекою інтерфейсу користувача, Angular – повномасштабним front-end фреймворком, що надає власні інструменти для всіх пов’язаних з розробкою веб-додатків функцій, а Vue – прогресивним фреймворком, реалізованим як додаткова розмітка для HTML.

Всі три фреймворки можуть використовуватися практично взаємозамінно для створення компонентних frontend-додатків з розширеними можливостями інтерфейсу користувача. Однак остаточний вибір залежить від вимог проекту та переваг розробника.

Кожен фреймворк має різну архітектуру, продуктивність у різних сценаріях, екосистему та інструменти, які ми намагатимемося розглянути у цій статті, щоб краще зрозуміти їх зручність використання.

Архітектура

1. React

Як видно з офіційного прикладу Hello World нижче, React не вимагає певної структури проекту, і ви можете почати використовувати його всього з декількох рядків коду.

ReactDOM.render(

  <h1>Hello, world!</h1>,

  document.getElementById('root')
);

React-елементи є найбільш базовими складовими React-додатків. Оскільки React DOM забезпечує їхнє ефективне оновлення при кожній зміні, вони є більш потужними, ніж стандартні елементи DOM.

Більші будівельні блоки, відомі як компоненти, визначають незалежні складові, що багаторазово використовуються, які використовуються у всьому додатку. Вони приймають вхідні дані, відомі як пропи, та створюють елементи, які згодом представлені користувачеві. Найбільш поширений варіант структурування проекту полягає у розбитті інтерфейсу на такі компоненти як імпорт в основний компонент App.js та його наступний рендер у React DOM.

React побудований на JavaScript, але також широко використовується JSX (JavaScript XML), розширення граматики, що дозволяє створювати елементи, що одночасно містять JavaScript і HTML. React JavaScript API та блоки, написані за допомогою JSX, сумісні, але здебільшого JSX більш зручний для використання та дозволяє уникнути написання довгого та, можливо, неінтуїтивного створення HTML елементів за допомогою JavaScript. Для порівняння нижче представлений той самий компонент написаний за допомогою JSX:

const Hello = (props) => {
    return
<div>Hello {props.toWhat}</div>
; } const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Hello toWhat="World" />);

та React JavaScript API:

const Hello = (props) => {
    return React.createElement('div', null, `Hello ${props.toWhat}`);
}

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render React.createElement(Hello, {toWhat: 'World'}, null));

React також підтримує TypeScript, але, на щастя менш досвідчених розробників, його використання є опціональним.

2. Vue

Основна бібліотека Vue.js зосереджена лише на шарі View. Її називають прогресивним фреймворком, тому що ми можемо розширити її функціональність за допомогою офіційних та сторонніх бібліотек, таких як Vue Router або Vuex, щоб перетворити її на справжній фреймворк.

Хоча Vue не пов’язаний із патерном MVVM (Model-View-ViewModel), його дизайн був частково натхненний ним. У Vue ви працюватимете в основному на рівні ViewModel, щоб переконатися, що дані програми обробляються таким чином, щоб фреймворк міг відобразити актуальний вид і стан програми за мінімальну кількість ре-рендерів.

Синтаксис шаблонів Vue дозволяє створювати компоненти, поєднуючи звичний HTML зі спеціальними директивами та можливостями. Цей синтаксис шаблонів є кращим, хоча чистий JavaScript та JSX також підтримуються. Компоненти Vue невеликі, самодостатні і можуть бути повторно використані у всьому додатку. Унікальною деталлю Vue є так звані Однофайлові компоненти (SFC) з розширенням .vue, які складаються з трьох частин – template, script та style , значення яких інтуїтивно зрозуміле кожному розробнику.

Типовий формат .vue файлу наведений нижче:

<template> 
... 
</template>  


<script>
... 
</script>

 
<style>
...
</style>

Template – написаний на розширеній версії HTML, він є директивою для фреймворку у тому, як проводити остаточну розмітку компонента з урахуванням його внутрішнього стану. Дозволяє створювати розмітку щодо умов або динамічно за допомогою vue-директив (v-if, v-for та ін.).

Script – призначений для написання логіки компонента та контролю його стану. Vue як і React дозволяє опціонально використовувати JavaScript, і TypeScript.

Style – вміщує CSS (так підтримує написання стилів на CSS пре-процессорах). Написані стилі енкапсулируются в компоненті не впливають інші компоненти.

Багатьом концепт приміщення всього необхідного коду для функціонування компонента в одному файлі видасться досить зручним. SFC є рекомендованим способом організації коду в Vue.js проектах, особливо великих. Як і у випадку з React, рекомендовано використовувати основний компонент App.vue для рендерування вашої програми.

3. Angular

AngularJS, оригінальний фреймворк, є MVC (Model-View-Controller) фреймворком. Але в Angular 2 немає суворого зв’язку з MV*-патернами, оскільки він також ґрунтується на компонентах.

Проекти в Angular структуровані на Модулі, Компоненти та Сервіси. Кожна програма Angular має як мінімум один рутовий компонент і один рутовий модуль.

Angular модулі – це контейнери для цілісного блоку коду, призначеного для домену програми, робочого процесу або тісно пов’язаного набору можливостей. Вони можуть містити компоненти, сервіс-провайдери та інші файли коду, сфера застосування яких визначається модулями, які вони містять. Вони можуть імпортувати функціональність, що експортується з інших модулів, або навпаки.

Кожен компонент в Angular містить шаблон, логіку з метаданими та опціонально стилі, розділені на окремі файли на відміну від Vue. Метадані для компонента вказують Angular, де знайти будівельні блоки, необхідні створення і уявлення виду компонента. Шаблони Angular написані на HTML, але також можуть включати синтаксис шаблонів Angular зі спеціальними директивами, як і у випадку з Vue.

Сервіси Angular використовуються компонентами для делегування завдань, таких як отримання даних або перевірка введення. Вони є окремою частиною програм Angular, на відміну від React та Vue, яким потрібно встановлювати додаткові бібліотеки для досягнення подібних цілей. Хоча Angular не змушує їх використання, настійно рекомендується структурувати додатки як набір окремих сервісів, які можна використовувати повторно.

Angular побудований на TypeScript, тому рекомендується використовувати його, хоча звичайний JavaScript також підтримується.

Екосистема

Ви можете заощадити багато часу при розробці програм, використовуючи Open Source ПЗ. Оскільки вони пройшли через численні вдосконалення і були використані в багатьох проектах, вони зазвичай перевершують кастомно розроблені компоненти. Дуже важливо враховувати доступність попередньо створених елементів, тем та інших інструментів, які можуть спростити створення вашої програми.

1. React

Глобальне керування станом часто використовується у зовнішніх програмах для зберігання таких даних, як інформація про користувача, токени і т.д. Redux – найпопулярніший проект управління глобальним станом JavaScript. Більшість React-розробників використовують офіційний React-біндинг для Redux, який підтримує команда проекту.

Оскільки React дуже популярний, за допомогою простого пошуку в Гуглі або на Гітхабі дуже легко знаходити готові компоненти та пекеджі практично на будь-який смак та необхідність.

Екосистема React також включає React Native, що дозволяє створювати нативні програми для iOS та Android написані на React. Таким чином, React може стати чудовим вибором для створення мобільних програм з використанням веб-технологій.

React є частиною стека MERN, до якого входять MongoDB, ExpressJS, React та NodeJS. Перевагою даного стеку є єдина мова програмування Javascript.

2. Vue

Хоча Redux може бути використаний з Vue, проте офіційного біндингу, як у випадку з React, немає. Але не варто переживати, оскільки існує Vuex – офіційний аналог Redux, створений спеціально для програм Vue і підтримується командою розробки Vue.js.

У перші дні існування Vue було складніше знайти готові до використання компоненти. Але з того часу, як спільнота зросла, з’явився широкий вибір компонентів та бібліотек, які можуть бути використані для прискорення розробки.

Для розробки мобільних програм існує перспективний проект під назвою Weex, розроблений компанією Alibaba. Однак Weex далеко не такий зрілий та потужний, як React Native. Більше того, оскільки проект розробляється та використовується більше в Китаї, складніше знайти документацію та вирішення проблем англійською мовою.

Vue часто використовується з Laravel завдяки їхній гарній інтеграції. Laravel пропонує повну JavaScript та CSS підтримку, що дозволяє використовувати Vue у Laravel проектах.

3. Angular

Для управління станами в Angular також є офіційний проект NgRx, натхненний Redux.

Як і у випадку з Vue і React, існує безліч готових до використання компонентів, які легко імпортуються в Angular проекти. Однак варто пам’ятати про наявність багатьох офіційних компонентів у бібліотеці Angular Material. Це офіційний проект від Google, який пропонує готові компоненти (кнопки, списки, діалогові вікна тощо) для програм Angular.

NativeScript є оптимальним варіантом для створення кросплатформових мобільних додатків у Angular. Він також може бути використаний з Vue, але підтримка Angular більш розвинена.

Angular є частиною відомого стека MEAN, що складається також із MongoDB, ExpressJS та NodeJS. Подібно до стеку MERN, він повністю покладається на JavaScript як для фронтенду, так і для бекенда.

Продуктивність

Продуктивність фреймворку чи бібліотеки – ще один фактор, який потрібно враховувати. Продуктивність часто не має значення, особливо якщо ви працюєте над невеликим проектом. Проте продуктивність ставатиме проблемою у міру збільшення масштабу та складності проекту.

Однак варто пам’ятати, що слідувати кращим практикам розробки і стежити за якістю коду часто більш важливо, ніж вибір фреймворку для кращої продуктивності програми. Тим не менш, розглянемо деякі показники продуктивності.

1. Бенчмарк JS фреймворків

Проект JS Framework Benchmark є гарним способом переглянути порівняння продуктивності різних фреймворків, за допомогою виконання базових операцій над таблицею з 1000 рандомізованих записів.

React і Angular справляються значно гірше за Vue зі свапом рядків і по суті це єдині суттєві відмінності в бенчмарках рендерингу таблиці – здебільшого вони не дадуть сильно помітних результатів. Єдине, можна сказати, що операція вибору рядків у таблиці є досить поширеною, що і ставить React в ледь помітне програшне становище.

Також React і Vue демонструють високі показники використання пам’яті та швидкості ініціалізації, проте Angular вимагає для цього більше часу. Базовий скрипт запускається за 150-200 мілісекунд, і Angular більш вимогливий до пам’яті найчастіше через те, що це повноцінний фреймворк із функціоналом, для якого React або Vue проекти зазвичай встановлюють сторонні бібліотеки за потребою.

2. Дослідження продуктивності Perf Track

Проект Perf Track від Google Chrome Labs() ставить за мету перевірити продуктивність вебсайтів, написаних на популярних фреймворках. Давайте подивимося на дані нижче:

React метрика React метрика

 

Vue метрика Vue метрика

 

angular метрика Angular метрика

 

Варто зауважити, що наведені дані були зібрані на момент 1 листопада 2020 року для мобільних пристроїв і на них впливає не тільки вибір фреймворку, але і безліч інших факторів. Для третього пункту, непередбаченого зсуву лейауту сторінки при навантаженні елементів, вибір фрейворку зовсім мало впливає. Однак, спробуємо зробити деякі висновки.

Перше промальовування контенту та найбільше промальовування контенту показує, що Vue та React краще справляються з провантаженням і рендерингом сторінки, ніж Angular, якому зазвичай потрібно більше часу, щоб підготувати програму. Затримкою першого введення (3 пункт) є тимчасовий проміжок між процесом користувача (клік на кнопку, введення даних) і реакцією сторінки нею і, на щастя, все 3 фреймворка показують винятково позитивні результати за цим параметром. Також варто зауважити, що понад 70% додатків на Vue завантажують менше 1MB JavaScript для своєї роботи, тоді як інші 2 фреймворки та Angular зокрема зазвичай займають набагато більше пам’яті. Однак, потрібно розуміти, що це може означати, що більшість маленьких проектів написані на Vue, тоді як Angular використовується для більш потужних проектів.

3. Додаткові прийоми покращення продуктивності

Основними двома техніками, що покращують роботу фронтенд додатків, є SSR (server-side rendering) та віртуалізація. Рендеринг за сервера насправді є здатність докладання скомпілювати HTML-файли з сервера на повністю провантажену сторінку для користувача, а віртуалізацією є навантаження компонентів у міру їх вимоги (наприклад у міру скролінгу сторінки).

React має за замовчуванням офіційний пекедж ReactDOMServer для рендерингу на стороні сервера, а для віртуалізації багато хто використовує сторонні бібліотеки React-Virtualized та React-Window

Vue також за замовчуванням має SSR пекедж Server-Renderer, проте з віртуалізацією потенційні розробники можуть мати проблеми, оскільки навіть найпопулярніша бібліотека Vue Virtual Scroll List має кілька багів і не настільки стабільна, як її аналоги для інших фреймворків.

Angular вкотре підтверджує своє звання повноцінного фреймворку, маючи офіційний Angular Universal для серверного рендерингу та вбудовані компоненти для віртуалізації.

Складність навчання та популярність

Важливою складовою при виборі фреймворку для девелоперів, які не мають попереднього досвіду, є концепти, які кожен привносить фреймворк, а також активність спільноти.

1. React

На перший погляд може здатися, що React – найпростіший у використанні фреймворк, що просто імпортуємо бібліотеку, і можна писати JavaScript з використанням React API. Однак на самому початку ми вже розглянули, наскільки незручно виглядає простий Hello world! Приклад, написаний на чистому JavaScript, тому кожен початківець React девелопер повинен змиритися з фактом, що альтернативи навчанню JSX немає, оскільки його використання є аксіомою у суспільстві для створення HTML-лейауту компонентам. Спочатку необхідність частково переходити на «мікс» з JavaScript та HTML може здатися дещо неінтуїтивною. З іншого боку, починаючи з версії 16.8 React вводить функціональні компоненти,

const exampleComponent = (props) => {

   ... // JavaScript 

   return(

    ... // JSX 

  );

};  export default exampleComponent;

Також варто зауважити, що версія 17.0 привнесла можливість часткового апгрейду програми, що дозволяє зберегти функціонал, що покладається на застарілі концепти, і робить React добрим вибором для довгострокової перспективи.

React є найчастіше завантажуваним фреймворком зі статистики npm, і це означає, що у користувачів не повинно бути проблем з пошуком рішень на можливі проблеми при девелопменті, так само як і активність спільноти дозволяє не тільки успішно шукати, але й ставити свої питання на популярних платформах типу StackOverflow.

2. Vue

Одним із концептів, з яким потрібно познайомитися початківцям Vue розробникам, є розширений HTML синтаксис із директивами. Більшість основних Vue директив інтуїтивно зрозумілі – v-if для рендерингу за умови, v-for для рендерингу в циклі, v-on для біндингу функціоналу до івент листенерів тощо. Наявність лейауту, функціоналу та стилів в одному .vue файлі з інтуїтивним синтаксисом також робить розробку кожного компонента максимально простим і без перескакування між файлами.

З релізом Vue 3 у 2020 році, творці фреймворку вирішили безліч проблем, які мала спільнота з використанням Vue для великих проектів, ввівши можливості для ширшого реюзу функціоналу між компонентами. Також було покращено підтримку таких проектів за допомогою рефакторингу вихідного коду Vue на TypeScript.

Так як Vue є «наймолодшим» фреймворком, розмір спільноти природно менший, ніж у React і Angular, і загальна популярність старої версії Vue 2 в основному серед китайських користувачів також створювала деякі проблеми в обміні знаннями та пошуку відповідей на запитання. Однак за тією самою статистикою npm – Vue є лідером у прирості завантажень за останній рік. Значні поліпшення Vue 3 дозволяють бути впевненими, що Vue скоро може обігнати за популярністю Angular.

3. Angular

Звання повноцінного фреймворку не дається легко, і Angular розробникам доводиться знайомитись з багатьма концептами. Angular очікує, що розробники дотримуватимуться певної структури в написанні коду і постійно використовуватимуть модулі з сервісами, крім компонентів. Як і у випадку з Vue необхідно вивчити додатковий HTML синтаксис, що складається з ng-* директив. І варто пам’ятати, що Angular працює найкраще з TypeScript і у разі вибору цього фреймворку потрібно обов’язково ознайомитися з TypeScript. Також складність вивчення підвищує широке використання RxJS – бібліотеки для реактивного програмування, яка дозволить зручно організувати роботу з подіями та асинхронним кодом і є потужнішою, але водночас складною альтернативою промісам.

Будучи старішим фреймворком, ніж React або Vue, і еволюціонувавши зі свого прототипу Angular.js, Angular може похвалитися величезним співтовариством, і він все ще є популярним вибором фреймворку для великих проектів. Однак у першої версії Angular була досить відома проблема – складність апгрейду проекту на новіші версії, яка згодом була виправлена ​​в Angular 2 і вище. Проте останнім часом безліч проектів вважають за краще використовувати React або Vue, особливо ті, що починають як старт-апи з невеликими командами.


Ми розглянули з вами низку основних аспектів, що розрізняють React, Vue та Angular. Насамкінець хочеться сказати, що всі 3 фреймворки знаходяться в активній розробці і регулярно отримують апдейти, тому в поточних реаліях можна без зайвого занепокоєння використовувати будь-який з них. Неможливо передбачити довгострокову релевантність будь-якого з фреймворків, але важливо помітити, що Angular вже не росте так само швидко, як колись, a Vue навпаки розвивається активніше за інших, переймаючи корисні концепти у React і Angular.

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

Переклад статті “React vs Vue vs Angular

Redux Toolkit як засіб ефективної розробки

Redux

В даний час розробка левової частки веб-застосунків, заснованих на фреймворку React, ведеться з використанням бібліотеки Redux. Ця бібліотека є найпопулярнішою реалізацією FLUX-архітектури і, незважаючи на низку очевидних переваг, має дуже суттєві недоліки, такі як:

  • складність і “багатослівність” рекомендованих патернів для написання та організації коду, що тягне за собою велику кількість бойлерплейту;
  • відсутність вбудованих засобів управління асинхронною поведінкою та побічними ефектами, що призводить до необхідності вибору відповідного інструменту з безлічі аддонів, написаних сторонніми розробниками.

Для усунення цих недоліків розробники Redux представили бібліотеку Redux Toolkit. Цей інструмент є набір практичних рішень та методів, призначених для спрощення розробки додатків з використанням Redux. Розробники цієї бібліотеки мали на меті спростити типові випадки використання Redux. Цей інструмент не є універсальним рішенням у кожному з можливих випадків використання Redux, але дозволяє спростити код, який потрібно написати розробнику.

У цій статті ми розповімо про основні інструменти, що входять до Redux Toolkit, а також, на прикладі фрагмента нашої внутрішньої програми, покажемо, як їх використовувати у коді, що вже є.

Коротко про бібліотеку

Коротка інформація про Redux Toolkit:

  • до релізу бібліотека називалася redux-starter-kit;
  • реліз відбувся наприкінці жовтня 2019 року;
  • Бібліотека офіційно підтримується розробниками Redux.

Згідно з заявою розробників Redux Toolkit виконує такі функції:

  • допомагає швидко розпочати використання Redux;
  • спрощує роботу з типовими завданнями та кодом Redux;
  • дозволяє використовувати найкращі практики Redux за замовчуванням;
  • пропонує рішення, які зменшують недовіру до бойлерплейтів.

Redux Toolkit надає набір як спеціально розроблених, так і додає ряд інструментів, що добре себе зарекомендували, які зазвичай використовуються спільно з Redux. Такий підхід дозволяє розробнику вирішити, як і які інструменти використовувати у своєму додатку. Під час цієї статті ми будемо відзначати які запозичення використовує дана бібліотека. Більш повну інформацію та залежності Redux Toolkit можна отримати з опису пакету @reduxjs/toolkit.

Найбільш значущими функціями, які надає бібліотека Redux Toolkit є:

  • configureStore – функція, призначена спростити процес створення та налаштування сховища;
  • createReducer — функція, що допомагає лаконічно та зрозуміло описати та створити ред’юсер;
  • createAction – повертає функцію творця дії для заданого рядка типу дії;
  • createSlice – поєднує в собі функціонал createAction і createReducer;
  • createSelector – функція з бібліотеки Reselect, переекспортована для простоти використання.

Також варто відзначити, що Redux Toolkit повністю інтегрований з TypeScript. Докладнішу інформацію про це можна отримати з розділу Usage With TypeScript офіційної документації.

Застосування

Розглянемо використання бібліотеки Redux Toolkit на прикладі фрагмента програми, що реально використовується React Redux.

Примітка. Далі у статті наводиться вихідний код як без використання Redux Toolkit, так і з використанням, що дозволить краще оцінити позитивні та негативні сторони використання цієї бібліотеки.

Завдання

В одному з наших внутрішніх додатків виникла потреба додавати, редагувати і відображати інформацію про релізи програмних продуктів, які ми випускаємо. Для кожної з цих дій були розроблені окремі функції API, результати виконання яких потрібно додавати в Redux store. Як засіб управління асинхронною поведінкою та побічними ефектами будемо використовувати Thunk.

Створення сховища

Початковий варіант вихідного коду, що здійснює створення сховища, виглядав наступним чином:

import {
  createStore, applyMiddleware, combineReducers, compose,
} from 'redux';
import thunk from 'redux-thunk';
import * as reducers from './reducers';

const ext = window.__REDUX_DEVTOOLS_EXTENSION__;
const devtoolMiddleware = 
  ext && process.env.NODE_ENV === 'development' ? ext() : f => f;

const store = createStore(
 combineReducers({
   ...reducers,
 }),
 compose(
   applyMiddleware(thunk),
   devtoolMiddleware
 )
);

Якщо уважно поглянути на наведений код, можна побачити досить довгу послідовність дій, яку необхідно зробити, щоб сховище було повністю налаштовано. Redux Toolkit містить інструмент, покликаний спростити цю процедуру, а саме: функцію configureStore.

Функція configureStore

Даний інструмент дозволяє автоматично комбінувати редьюсери, додати мідлвари Redux (за замовчуванням включає redux-thunk), а також використовувати розширення Redux DevTools. Як вхідні параметри функція configureStore приймає об’єкт з такими властивостями:

  • reducer — набір ред’юсерів користувача,
  • middleware – опціональний параметр, що задає масив мідлварів, призначених для підключення до сховища,
  • devTools — параметр логічного типу, що дозволяє увімкнути встановлене у браузер розширення Redux DevTools (значення за промовчанням — true),
  • preloadedState – опціональний параметр, що задає початковий стан сховища,
  • enhancers – опціональний параметр, що задає набір підсилювачів.

Для отримання найбільш популярного списку мідлвар можна скористатися спеціальною функцією getDefaultMiddleware, що також входить до складу Redux Toolkit. Ця функція повертає масив із включеними за замовчуванням до бібліотеки Redux Toolkit мідлварами. Перелік цих мідлвар відрізняється залежно від того, в якому режимі виконується ваш код. У режимі production масив складається тільки з одного елемента – thunk. У режимі development на момент написання статті список поповнюється такими мідлварами:

  • serializableStateInvariant — інструмент, спеціально розроблений для використання в Redux Toolkit і призначений для перевірки дерева станів на наявність несеріалізованих значень, таких як функції, Promise, Symbol та інші значення, що не є простими JS-даними;
  • immutableStateInvariant – мідлвар з пакету redux-immutable-state-invariant , призначений для виявлення мутацій даних, що містяться в сховищі.

Для завдання списку мідлварів, що повертається, функція getDefaultMidlleware приймає об’єкт, що визначає перелік включених мідлварів і налаштування для кожного з них. Докладніше з цією інформацією можна ознайомитись у відповідному розділі офіційної документації.

Тепер перепишемо ділянку коду, відповідальний створення сховища, скориставшись описаними вище інструментами. В результаті отримаємо наступне:

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import * as reducers from './reducers';

const middleware = getDefaultMiddleware({
  immutableCheck: false,
  serializableCheck: false,
  thunk: true,
});

export const store = configureStore({
 reducer: { ...reducers },
 middleware,
 devTools: process.env.NODE_ENV !== 'production',
});

На прикладі даної ділянки коду добре видно, що функція configureStore вирішує такі проблеми:

  • необхідність комбінувати ред’юсери, автоматично викликаючи combineReducers,
  • необхідність комбінувати мідлвари, автоматично викликаючи applyMiddleware.

А також дозволяє зручніше включити розширення Redux DevTools, використовуючи функцію composeWithDevTools з пакету redux-devtools-extension. Все сказане вище свідчить про те, що використання цієї функції дозволяє зробити код більш компактним і зрозумілим.

На цьому створення та налаштування сховища завершено. Передаємо його у провайдер і переходимо далі.

Дії, творці дій та редьюсер

Тепер розглянемо можливості Redux Toolkit щодо розробки дій, творців дій і редьюсера. Початковий код без використання Redux Toolkit був організований у вигляді файлів actions.js і reducers.js. Вміст файлу actions.js виглядав так:

import * as productReleasesService from '../../services/productReleases';

export const PRODUCT_RELEASES_FETCHING = 'PRODUCT_RELEASES_FETCHING';
export const PRODUCT_RELEASES_FETCHED = 'PRODUCT_RELEASES_FETCHED';
export const PRODUCT_RELEASES_FETCHING_ERROR =
  'PRODUCT_RELEASES_FETCHING_ERROR';

…

export const PRODUCT_RELEASE_UPDATING = 'PRODUCT_RELEASE_UPDATING';
export const PRODUCT_RELEASE_UPDATED = 'PRODUCT_RELEASE_UPDATED';
export const PRODUCT_RELEASE_CREATING_UPDATING_ERROR =
  'PRODUCT_RELEASE_CREATING_UPDATING_ERROR';

function productReleasesFetching() {
  return {
    type: PRODUCT_RELEASES_FETCHING
  };
}

function productReleasesFetched(productReleases) {
  return {
    type: PRODUCT_RELEASES_FETCHED,
    productReleases
  };
}

function productReleasesFetchingError(error) {
  return {
    type: PRODUCT_RELEASES_FETCHING_ERROR,
    error
  }
}

…

export function fetchProductReleases() {
  return dispatch => {
    dispatch(productReleasesFetching());
    return productReleasesService.getProductReleases().then(
      productReleases => dispatch(productReleasesFetched(productReleases))
    ).catch(error => {
      error.clientMessage = "Can't get product releases";
      dispatch(productReleasesFetchingError(error))
    });
  }
}

…

export function updateProductRelease(
  id, productName, productVersion, releaseDate
) {
  return dispatch => {
    dispatch(productReleaseUpdating());
    return productReleasesService.updateProductRelease(
      id, productName, productVersion, releaseDate
    ).then(
      productRelease => dispatch(productReleaseUpdated(productRelease))
    ).catch(error => {
      error.clientMessage = "Can't update product releases";
      dispatch(productReleaseCreatingUpdatingError(error))
    });
  }
}

Вміст файлу reducers.js до використання Redux Toolkit:

const initialState = {
 productReleases: [],
 loadedProductRelease: null,
 fetchingState: 'none',
 creatingState: 'none',
 updatingState: 'none',
 error: null,
};

export default function reducer(state = initialState, action = {}) {
 switch (action.type) {
   case productReleases.PRODUCT_RELEASES_FETCHING:
     return {
       ...state,
       fetchingState: 'requesting',
       error: null,
     };
   case productReleases.PRODUCT_RELEASES_FETCHED:
     return {
       ...state,
       productReleases: action.productReleases,
       fetchingState: 'success',
     };
   case productReleases.PRODUCT_RELEASES_FETCHING_ERROR:
     return {
       ...state,
       fetchingState: 'failed',
       error: action.error
     };

…

   case productReleases.PRODUCT_RELEASE_UPDATING:
     return {
       ...state,
       updatingState: 'requesting',
       error: null,
     };
   case productReleases.PRODUCT_RELEASE_UPDATED:
     return {
       ...state,
       updatingState: 'success',
       productReleases: state.productReleases.map(productRelease => {
         if (productRelease.id === action.productRelease.id)
           return action.productRelease;
         return productRelease;
       })
     };
   case productReleases.PRODUCT_RELEASE_UPDATING_ERROR:
     return {
       ...state,
       updatingState: 'failed',
       error: action.error
     };
   default:
     return state;
 }
}

Як ми можемо бачити, саме тут міститься більшість бойлерплейта: константи типів дій, творці дій, знову константи, але вже у коді редьюсера на написання всього цього коду доводиться витрачати час. Частково цього бойлерплейту можна позбутися, якщо скористатися функціями createAction і createReducer, які також входять до складу Redux Toolkit.

Функція createAction

У наведеній ділянці коду використовується стандартний спосіб визначення дії Redux: спочатку окремо оголошується константа, що визначає тип дії, після чого – функція творця дії цього типу. Функція createAction об’єднує ці об’яви в одне. На вхід вона приймає тип дії та повертає творця дії для цього типу. Автор дії може бути викликаний або без аргументів, або з деяким аргументом (корисне навантаження), значення якого буде поміщено в полі payload, створеного дії. Крім того, автор дії перевизначає функцію toString(), так що тип дії стає його рядковим уявленням.

У деяких випадках може знадобитися написати додаткову логіку для налаштування значення корисного навантаження, наприклад, прийняти кілька параметрів для творця дії, створити випадковий ідентифікатор або отримати поточну мітку. Для цього createAction приймає необов’язковий другий аргумент – функцію, яка використовуватиметься для оновлення значення корисного навантаження. Докладніше про цей параметр можна ознайомитись в офіційній документації.
Використовуючи функцію createAction, отримаємо наступний код:

export const productReleasesFetching =
  createAction('PRODUCT_RELEASES_FETCHING');
export const productReleasesFetched =
  createAction('PRODUCT_RELEASES_FETCHED');
export const productReleasesFetchingError =
  createAction('PRODUCT_RELEASES_FETCHING_ERROR');

…

export function fetchProductReleases() {
  return dispatch => {
    dispatch(productReleasesFetching());
    return productReleasesService.getProductReleases().then(
      productReleases => dispatch(productReleasesFetched({ productReleases }))
    ).catch(error => {
      error.clientMessage = "Can't get product releases";
      dispatch(productReleasesFetchingError({ error }))
    });
  }
}
...

Функція createReducer

Тепер розглянемо ред’юсер. Як і наш приклад, редьюсери часто реалізуються з допомогою оператора switch, з одним регістром кожному за обробленого типу дії. Цей підхід працює добре, але не позбавлений бойлерплейта і схильний до помилок. Наприклад, легко забути описати випадок default або встановити початковий стан. Функція createReducer спрощує створення функцій редьюсера, визначаючи їх як таблиці пошуку функцій обробки кожного типу дії. Вона також дозволяє суттєво спростити логіку іммутабельного поновлення, написавши код у “мутабельному” стилі всередині ред’юсерів.

“Мутабельний” стиль обробки подій доступний завдяки використанню бібліотеки Immer. Функція обробник може або “мутувати” переданий state для зміни властивостей, або повертати новий state, як при роботі в іммутабельному стилі, але завдяки Immer реальна мутація об’єкта не здійснюється. Перший варіант набагато простіше для роботи та сприйняття, особливо при зміні об’єкта з глибокою вкладеністю.

Будьте уважні: повернення нового об’єкта із функції перекриває “мутабельні” зміни. Одночасне застосування обох методів поновлення стану не спрацює.

Як вхідні параметри функція createReducer приймає такі аргументи:

  • початковий стан сховища,
  • об’єкт, що встановлює відповідність між типами дій та редьюсерами, кожен із яких обробляє якийсь певний тип.

Скориставшись методом createReducer, отримаємо наступний код:

const initialState = {
 productReleases: [],
 loadedProductRelease: null,
 fetchingState: 'none',
 creatingState: 'none',
 loadingState: 'none',
 error: null,
};

const counterReducer = createReducer(initialState, {
 [productReleasesFetching]: (state, action) => {
   state.fetchingState = 'requesting'
 },
 [productReleasesFetched.type]: (state, action) => {
   state.productReleases = action.payload.productReleases;
   state.fetchingState = 'success';
 },
 [productReleasesFetchingError]: (state, action) => {
   state.fetchingState = 'failed';
   state.error = action.payload.error;
 },

…

 [productReleaseUpdating]: (state) => {
   state.updatingState = 'requesting'
 },
 [productReleaseUpdated]: (state, action) => {
   state.updatingState = 'success';
   state.productReleases = state.productReleases.map(productRelease => {
     if (productRelease.id === action.payload.productRelease.id)
       return action.payload.productRelease;
     return productRelease;
   });
 },
 [productReleaseUpdatingError]: (state, action) => {
   state.updating = 'failed';
   state.error = action.payload.error;
 },
});

Як бачимо, використання функцій createAction і createReducer істотно вирішує проблему написання зайвого коду, але проблема попереднього створення констант все одно залишається. Тому розглянемо більш потужний варіант, що поєднує в собі генерацію і творців дій та редьюсера – функція createSlice.

Функція createSlice

Як вхідні параметри функція createSlice приймає об’єкт із наступними полями:

  • name – простір імен створюваних дій ( ${name}/${action.type});
  • initialState – початковий стан ред’юсера;
  • reducers – об’єкт з обробниками. Кожен обробник приймає функцію з аргументами state і action, action містить у собі дані у властивості payload та ім’я події у властивості name. Крім того, є можливість попередньої зміни даних, отриманих з події, перед їх потраплянням до редьюсера (наприклад, додати id до елементів колекції). Для цього замість функції необхідно передати об’єкт з полями reducer та prepare, де reducer – це функція-обробник дії, а prepare – функція-обробник корисного навантаження, що повертає оновлений payload;
  • extraReducers – об’єкт, що містить редьюсери іншого зрізу. Цей параметр може знадобитися в разі потреби оновлення об’єкта, що стосується іншого зрізу. Докладніше про цю можливість можна дізнатися з відповідного розділу офіційної документації.

Результатом роботи функції є об’єкт, званий “зріз (slice)”, з наступними полями:

  • name – ім’я зрізу,
  • reducer – ред’юсер,
  • actions – набір дій.

Використовуючи цю функцію для вирішення нашого завдання, отримаємо наступний вихідний код:

const initialState = {
 productReleases: [],
 loadedProductRelease: null,
 fetchingState: 'none',
 creatingState: 'none',
 loadingState: 'none',
 error: null,
};

const productReleases = createSlice({
 name: 'productReleases',
 initialState,
 reducers: {
   productReleasesFetching: (state) => {
     state.fetchingState = 'requesting';
   },
   productReleasesFetched: (state, action) => {
     state.productReleases = action.payload.productReleases;
     state.fetchingState = 'success';
   },
   productReleasesFetchingError: (state, action) => {
     state.fetchingState = 'failed';
     state.error = action.payload.error;
   },

…

   productReleaseUpdating: (state) => {
     state.updatingState = 'requesting'
   },
   productReleaseUpdated: (state, action) => {
     state.updatingState = 'success';
     state.productReleases = state.productReleases.map(productRelease => {
       if (productRelease.id === action.payload.productRelease.id)
         return action.payload.productRelease;
       return productRelease;
     });
   },
   productReleaseUpdatingError: (state, action) => {
     state.updating = 'failed';
     state.error = action.payload.error;
   },
 },
});

Тепер витягнемо із створеного зрізу творці дій та ред’юсер.

const { actions, reducer } = productReleases;

export const {
  productReleasesFetched, productReleasesFetching,
  productReleasesFetchingError,
…
  productReleaseUpdated,
  productReleaseUpdating, productReleaseUpdatingError
} = actions;

export default reducer;

Вихідний код творців дій, що містять виклики API, не змінився, за винятком способу передачі параметрів під час надсилання дій:

export const fetchProductReleases = () => (dispatch) => {
 dispatch(productReleasesFetching());
 return productReleasesService
   .getProductReleases()
   .then((productReleases) => dispatch(productReleasesFetched({ productReleases })))
   .catch((error) => {
     error.clientMessage = "Can't get product releases";
     dispatch(productReleasesFetchingError({ error }));
   });
};

…

export const updateProductRelease = (id, productName, productVersion, releaseDate) => (dispatch) => {
 dispatch(productReleaseUpdating());
 return productReleasesService
   .updateProductRelease(id, productName, productVersion, releaseDate)
   .then((productRelease) => dispatch(productReleaseUpdated({ productRelease })))
   .catch((error) => {
     error.clientMessage = "Can't update product releases";
     dispatch(productReleaseUpdatingError({ error }));
   });

Наведений вище код показує, що функція createSlice дозволяє позбутися значної частини бойлерплейта при роботі з Redux, що дозволяє не тільки зробити код компактнішим, лаконічнішим і зрозумілішим, але й витрачати менше часу на його написання.

Підсумок

На завершення цієї статті, хотілося б сказати, що незважаючи на те, що бібліотека Redux Toolkit не вносить нічого нового в управління сховищем, вона надає ряд набагато зручніших засобів для написання коду, ніж були до цього. Дані засоби дозволяють не тільки зробити процес розробки більш зручним, зрозумілим і швидким, але і більш ефективним, за рахунок наявності в бібліотеці ряду інструментів, що добре зарекомендували себе раніше.

Переклад статті “Redux Toolkit как средство эффективной Redux-разработки

Картка Binance тепер підтримує XRP, SHIB і AVAX

Картка Binance

Binance додала XRPSHIB і AVAX до списку підтримуваних криптовалют на Binance Cards, починаючи з 07:00 (UTC) 05 серпня 2022 року.
  • На даний момент картка Binance доступна лише для користувачів Binance, які проживають в ЄЕЗ, і українських біженців, які проживають у країні ЄЕЗ (Binance Refugee Crypto Card )Ви можете зареєструватися на Binance Card тут .
  • Станом на 07:00 (UTC) 2022-08-05 на Binance Card є 14 підтримуваних криптовалют: ADA, AVAX, BNB, BTC, BUSD, DOT, ETH, LAZIO, PORTO, SANTOS, SHIB, SXP, USDT і XRP .
  • Щоб витратити нещодавно підтримані криптовалюти за допомогою Binance Card, перейдіть до Інформаційної панелі картки > Пріоритет платежу > Редагувати, щоб додати, видалити або ранжувати криптовалюти для витрат відповідно до власних уподобань.
  • Користувачі повинні мати принаймні шість криптовалют у списку пріоритетів платежів. Можна вибрати максимум 12 криптовалют.
  • З додаванням XRP, SHIB і AVAX до Binance Card поточні параметри пріоритету платежу для наявних користувачів картки Binance не зміняться.
  • Binance Card випускається ліцензованою організацією UAB «Finansinės paslaugos Contis» і розповсюджується Swipe. Використання вами Binance Card регулюється умовами, викладеними в Угоді власника картки.

React Storybook

За допомогою React Storybook ви можете розробляти та проектувати компоненти інтерфейсу користувача за межами вашої програми в ізольованому середовищі. Це змінить спосіб розробки компонентів інтерфейсу користувача.

React Storybook
Ось так виглядає React Storybook.

Перш ніж ми почнемо

Сьогодні серверні системи стали дуже простими завдяки фреймворкам, таким як Meteor, і хмарним службам, таким як Firebase. Такі речі, як GraphQL і Falcor виведе серверні системи на новий рівень.

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

Просто порахуйте КІЛЬКІСТЬ РЯДКІВ , які ви написали для СТОРОНИ КЛІЄНТА, і ви зрозумієте, що я маю на увазі.

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

Це важко

Завдяки гарячому перезавантаженню ми можемо дуже швидко розробляти та ітерувати інтерфейс користувача. Зазвичай ми робимо це в нашому додатку.

Але побудувати компонент у програмі важко. Дозвольте мені показати вам проблему.

Уявіть, що ми створюємо компонент списку завдань. Отже, він має кілька станів, і нам потрібно змінити інтерфейс для них. Ось список штатів:

  • У списку немає елементів.
  • У списку є кілька елементів (він не порожній).
  • Деякі з цих пунктів завершено.
  • Усі пункти в списку виконано.

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

Я можу перерахувати багато таких проблем, як це. Але, я сподіваюся, ви зрозуміли суть.

Зустрічайте React Storybook

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

Після налаштування ви можете запустити консоль React Storybook, ввівши:

npm run book story

Потім він запустить веб-сервер на порту 9001 і виглядає так:

React Storybook

Після цього нам потрібно помістити наші компоненти (з їхніми різними станами ) у наведену вище консоль. Ми робимо це, пишучи набір історій.

Одна STORY має повертати елемент REACT. Потім ви взаємодієте з ним із React Storybook.

У наступному коді ми пишемо кілька історій для різних станів нашого компонента списку завдань.
(Тут наш компонент списку завдань називається «MainSection»)

import React from 'react';
import MainSection from '../MainSection';
import { storiesOf, action } from '@storybook';

storiesOf('MainSection', module)
  .add('all active', () => {
    const todoItems = [
      { id: 'one', text: 'Item One', completed: false },
      { id: 'two', text: 'Item Two', completed: false },
    ];

    return getMainSection(todoItems);
  })
  .add('some completed', () => {
    const todoItems = [
      { id: 'one', text: 'Item One', completed: false },
      { id: 'two', text: 'Item Two', completed: true },
    ];

    return getMainSection(todoItems);
  })
  .add('all completed', () => {
    const todoItems = [
      { id: 'one', text: 'Item One', completed: true },
      { id: 'two', text: 'Item Two', completed: true },
    ];

    return getMainSection(todoItems);
  });

function getMainSection(todos) {
  const actions = {
    clearCompleted: action('clearCompleted'),
    completeAll: action('completeAll')
  };

  return (

); }

Тоді ти маєш змогу взаємодіяти з ними у React Storybook ось так:

Storybook

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

Переклад статті “Introducing React Storybook

Binance Card для біженців із України

binance card

Binance запустила Binance Card для біженців із України, які були змушені переїхати до країн ЄЕЗ через війну з Росією 📣🇺🇦

Binance Card створена у партнерстві з Contis. Це дозволить переміщеним українцям здійснювати або отримувати криптоплатежі та здійснювати купівлю в роздрібних торгових точках в ЄЕЗ, які приймають оплату карткою.

🙏🏻 Binance Charity співпрацює з різними неурядовими організаціями, щоб надати грошову допомогу за допомогою Binance Card для біженців. До слова, створений Binance «Фонд екстреної допомоги Україні» уже зібрав більше $1 млн.

📩 Отримання та використання віртуальної та фізичної Binance Card безкоштовне. Щоб отримати картку, біженцям потрібно мати або зареєструвати акаунт на Binance, пройти повну верифікацію за українським документом та прив’язати до акаунта європейський номер телефона. Для отримання картки використовуйте вашу поточну адресу у країні ЄЕЗ.

➡️ Гайд із замовлення Binance Card

📹 Відеогайд

💳 Замовити Binance Card

Також слід зазначити, що біженці, які звернулися до місцевих некомерційних організацій та подали заявку на отримання криптовалютних карток Binance Refugee Card, отримають 75 BUSD (1 BUSD = $1) щомісно (225 BUSD протягом трьох місяців) як підтримку від біржі.

Figma plugin API людською мовою

Figma

Зіткнувся з написанням плагіна для Figma і дивуюся, навіщо так складно? Ну, взагалі-то, нічого особливо складного немає. Але цей TypeScript, це навіщо все? Встанови модулі, налаштуйте публікацію. Скільки я не думав, єдине пояснення, навіщо все це потрібне, це захист від дурня. Типу, кому треба – розбереться, а іншим і нема чого лізти, говнокод плодити.

Ну так ми розвіємо цей снобістський тренд і прорубаємо шлях в API Figma для всіх бажаючих.

Суть у тому, що якщо відкинути все зайве, то в сухому залишку нам знадобиться лише три файли:

Figma plugin API людською мовою

Точкою входу плагіна є файл manifest.json. Виглядати він має так:

{
 "name": "Simple Plugin",
 "api": "1.0.0",
 "main": "plugin.js",
 "ui": "index.html",
 "editorType": [
   "figma"
 ]
}

Поле name це ім’я плагіна, воно буде відображатися в меню і на корінці вікна плагіна.

У полі api ми вказуємо версію api, поки це 1.0.0.

Поле editorType містить масив із перерахуванням редакторів, для яких розробляється плагін. Ми вказуємо лише figma.

Два поля main і ui, що залишилися, містять шляхи до файлів самого плагіна. ui вказує на html з версткою та логікою вікна плагіна. main посилається на js з логікою взаємодії плагіна з Figma.

Почнемо їх вивчення з ui, а значить із файлу index.html:

<button>Click Me</button>
<script>
   document.querySelector('button').onclick = () => {
       alert('Hello world!!!')
   }
</script>

Я помістив у нього кнопку та скрипт, що обробляє клік по цій кнопці. Натиснувши кнопку, виводиться вікно з текстом “Hello World!!!”. Тут важливо зрозуміти, що скрипт повинен перебувати усередині html. Підвантажувати скрипт атрибутом src плагін не вміє.

Ну, і останній файл, який нам потрібен для роботи плагіна, це plugin.js.

figma.showUI (
   __html__,
   {width: 400, height: 400}
);

Він дуже простий, в ньому ми тільки даємо Figma команду, відкрити вікно з вмістом нашого html. Другим параметром метод showUI приймає об’єкт, у якому ми вказуємо необхідний розмір вікна.

Ось, власне, і все. Нам залишилося лише відкрити редактор Figma та перейти в меню Plugins -> Development -> Import plugin from manifest…

Figma plugin API людською мовою

Вибираємо наш manifest.json, після чого в тому ж меню знаходимо наш плагін.

Figma plugin API людською мовою

Наш плагін готовий.

З документацією API Figma можна познайомитись тут.