TypeScript, найкращі практики при написанні коду

TypeScript

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

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

Найкраща практика 1: сувора перевірка типів

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

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

Увімкнути режим суворої перевірки типів можна просто встановити параметр “strict”: true у файлі tsconfig.json (за замовчуванням має бути true). В результаті TS активує набір перевірок, що перехоплюють певні помилки, які інакше залишилися б непоміченими.

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

let userName: string = "John";
userName = 123; // TypeScript викине виключення, тому що "123" не є строкою.

Найкраща практика 2: виведення типів

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

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

Наприклад, у наступному фрагменті коду TS автоматично виведе тип name як string:
<prelet name = “John”;

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

Але тут треба пам’ятати, що виведення типів – це не чарівна паличка, і іноді краще вказувати типи явно, особливо при роботі зі складними варіантами або у випадках, коли потрібно використовувати певний тип.

Найкраща практика 3: лінтери

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

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

Найкраща практика 4: інтерфейси

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

Інтерфейс в TS визначає договір форми об’єкта. Він показує властивості та методи, якими об’єкт даного типу повинен володіти, і може використовуватися як тип змінної. Це означає, що у разі присвоєння об’єкту змінної з типом interface TS перевірятиме, щоб цей об’єкт мав усі вказані в даному інтерфейсі властивості та методи.

Ось приклад визначення та використання інтерфейсу:

interface User {
 name: string;
 age: number;
}
let user: User = {name: "John", age: 25};

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

Найкраща практика 5: псевдоніми типів (type alias)

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

Наприклад, псевдонім типу можна використовувати, щоб створити власний тип для точки у двовимірному просторі:

type Point = { x: number, y: number };
let point: Point = { x: 0, y: 0 };

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

type User = { name: string, age: number };
type Admin = { name: string, age: number, privileges: string[] };
type SuperUser = User & Admin;

Найкраща практика 6: кортежі

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

Наприклад, за допомогою кортежу можна уявити ту саму точку у двовимірному просторі:

let point: [number, number] = [1, 2];

З їх допомогою можна також представляти колекцію елементів декількох типів:

let user: [string, number, boolean] = ["Bob", 25, true];

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

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

let point: [number, number] = [1, 2];
let [x, y] = point;
console.log(x, y);

Найкраща практика 7: тип any

Іноді у нас немає всієї інформації про тип змінної, але використовувати її в коді все ж таки потрібно. У подібних випадках можна задіяти тип any. Однак, як і у випадку з будь-яким потужним інструментом, any необхідно бути дуже обережним.

Один з кращих прийомів полягає в обмеженні застосування any до конкретних випадків, в яких тип дійсно невідомий. Це буває при роботі зі сторонніми бібліотеками або даними, що динамічно генеруються. Крім того, буде зайвим додавати затвердження типів або тайп гарди (type guard), гарантуючи правильне використання змінної. Також по можливості намагайтеся максимально звузити тип змінної.

Наприклад:

function logData(data: any) {
    console.log(data);
}

const user = { name: "John", age: 30 };
const numbers = [1, 2, 3];

logData(user); // { name: "John", age: 30 }
logData(numbers); // [1, 2, 3]

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

Найкраща практика 8: тип unknown

Unknown– це рестриктивний тип, введений у TypeScript 3.0. Він більш обмежений у порівнянні з any і може позбавити вас від низки непередбачених помилок.

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

Наприклад, тип unknown можна використовувати для створення більш типобезпечної функції:

function printValue(value: unknown) {
 if (typeof value === "string") {
 console.log(value);
 } else {
 console.log("Not a string");
 }
}

З його допомогою також можна створювати типобезпечніші змінні:

let value: unknown = "hello";
let str: string = value; // Помилка: тип 'unknown' неможна присвоювати типу 'string'.

Найкраща практика 9: тип Object

Тип Object є вбудованою можливістю TypeScript, що дозволяє посилатися базовий тип об’єкта. З його допомогою можна підвищити типобезпечність коду, забезпечивши наявність у всіх об’єктів певних властивостей чи методів.

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

function printObject(obj: Object) {
 console.log(obj);
}

Тип Object також можна використовувати для створення більш типобезпечних змінних:

let obj: Object = { name: "John", age: 30 };
let str: string = obj.name; // валідно
let num: number = obj.age; // валідно

За допомогою нього ви можете забезпечити, щоб всі об’єкти мали певні властивості або методи, підвищивши тим самим типобезпеку коду.

Найкраща практика 10: тип never

TypeScript never є особливим типом, що представляє значення, яке ніколи не з’явиться. З його допомогою вказують, що функція замість стандартного повернення викидатиме помилку. Це чудовий спосіб, що дозволяє показати іншим розробникам (і компілятору), що функцію не можна використовувати певним чином.

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

function divide(numerator: number, denominator: number): number {
 if (denominator === 0) {
 throw new Error("Cannot divide by zero");
 }
 return numerator / denominator;
}

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

function divide(numerator: number, denominator: number): number | never {
 if (denominator === 0) {
 throw new Error("Cannot divide by zero");
 }
 return numerator / denominator;
}

Найкраща практика 11: оператор keyof

Оператор keyof є ефективною можливістю TypeScript, що дозволяє створювати тип, що представляє ключі об’єкта. З його допомогою можна проясняти, які властивості для об’єкта є допустимими.

Наприклад, keyof можна використовувати створення більш читаного і обслуговуваного типу об’єкта:

interface User {
 name: string;
 age: number;
}
type UserKeys = keyof User; // "name" | "age"

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

function getValue<T, K extends keyof T>(obj: T, key: K) {
 return obj[key];
}
let user: User = { name: "John", age: 30 };
console.log(getValue(user, "name")); // "John"
console.log(getValue(user, "gender")); // Помилка: аргумент з типом '"gender"' неможна присвоїти параметру з типом '"name" | "age"'.

Найкраща практика 12: Enums

Enums– Це перерахування, що дозволяють визначати в TS набір іменованих констант. З їх допомогою можна писати більш читаний та обслуговуваний код, даючи набору зв’язаних значень ім’я, що відображає загальний зміст.

Наприклад, enum можна визначити набір можливих значень статусу замовлення:

enum OrderStatus {
 Pending,
 Processing,
 Shipped,
 Delivered,
 Cancelled
}
let orderStatus: OrderStatus = OrderStatus.Pending;

Перерахування також можуть мати кастомний набір чисельних значень чи рядків.

enum OrderStatus {
 Pending = 1,
 Processing = 2,
 Shipped = 3,
 Delivered = 4,
 Cancelled = 5
}
let orderStatus: OrderStatus = OrderStatus.Pending;

Угода про іменування вимагає називати перерахування з великої літери і вказувати їх завжди в однині.

Найкраща практика 13: простор імен

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

Наприклад, простір імен можна використовувати для групування всього коду, пов’язаного з певним функціоналом:

namespace OrderModule {
 export class Order { /* … */ }
 export function cancelOrder(order: Order) { /* … */ }
 export function processOrder(order: Order) { /* … */ }
}
let order = new OrderModule.Order();
OrderModule.cancelOrder(order);

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

namespace MyCompany.MyModule {
 export class MyClass { /* … */ }
}
let myClass = new MyCompany.MyModule.MyClass();

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

Найкраща практика 14: допоміжні типи (utility types)

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

Наприклад, допоміжний тип Pick можна використовуватиме вилучення підмножини властивостей із типу об’єкта:

type User = { name: string, age: number, email: string };
type UserInfo = Pick<User, "name" | "email">;
Exclude дозволяє видалити з типу об'єкта властивості:
type User = { name: string, age: number, email: string };
type UserWithoutAge = Exclude<User, "age">;

Partial дає можливість зробити всі властивості типу необов’язковими:

type User = { name: string, age: number, email: string };
type PartialUser = Partial<User>;

Найкраща практика 15: Readonly та ReadonlyArray

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

За допомогою Readonly ми переводимо властивості об’єкта в стан «тільки для читання», виключаючи можливість зміни після створення. Такий прийом придатний, наприклад, при роботі з конфігурацією або постійними значеннями.

interface Point {
 x: number;
 y: number;
}
let point: Readonly<Point> = {x: 0, y: 0};
point.x = 1; // TypeScript видасть помилку, тому що "point.x" є read-only

ReadonlyArray аналогічно Readonly, але використовується для масивів.

let numbers: ReadonlyArray<number> = [1, 2, 3];
numbers.push(4); // TypeScript видасть помилку, тому що "numbers" є read-only

Найкраща практика 16: type guards

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

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

function isNumber(x: any): x is number {
 return typeof x === "number";
}
let value = 3;
if (isNumber(value)) {
 value.toFixed(2); // Завдяки тайп-гарду TypeScript знає, що "value" є числом.
}

Тайп-гарди також можна використовувати з операторами in і typeofinstanceof

Найкраща практика 17: узагальнені типи (generics)

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

Наприклад, узагальнену функцію можна використовуватиме створення масиву будь-якого типу:

function createArray<T>(length: number, value: T): Array<T> {
 let result = [];
 for (let i = 0; i < length; i++) {
 result[i] = value;
 }
 return result;
}
let names = createArray<string>(3, "Bob");
let numbers = createArray<number>(3, 0);

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

class GenericNumber<T> {
 zeroValue: T;
 add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

Найкраща практика 18: ключове слово infer

Ключове слово infer є просунутою можливістю TS, дозволяє витягти тип змінної в окремий тип.

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

type ArrayType<T> = T extends (infer U)[] ? U : never;
type MyArray = ArrayType<string[]>; // MyArray має тип string

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

type ObjectType<T> = T extends { [key: string]: infer U } ? U : never;
type MyObject = ObjectType<{ name: string, age: number }>; // MyObject має тип {name:string, age: number}

Найкраща практика 19: умовні типи

З допомогою умовних типів можна створювати нові типи з урахуванням умов інших типів, висловлюючи цим складні відносини з-поміж них.

Наприклад, умовний тип можна використовувати для отримання типу функції, що повертається:

type ReturnType<T> = T extends (…args: any[]) => infer R ? R : any;
type R1 = ReturnType<() => string>; // string
type R2 = ReturnType<() => void>; // void

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

type PickProperties<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];
type P1 = PickProperties<{ a: number, b: string, c: boolean }, string | number>; // "a" | "b"

Найкраща практика 20: відображені типи

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

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

type Readonly<T> = { readonly [P in keyof T]: T[P] };
let obj: { a: number, b: string } = { a: 1, b: "hello" };
let readonlyObj: Readonly<typeof obj> = { a: 1, b: "hello" };

Вони також дозволяють створювати новий тип, що представляє опціональну версію:

type Optional<T> = { [P in keyof T]?: T[P] };
let obj: { a: number, b: string } = { a: 1, b: "hello" };
let optionalObj: Optional<typeof obj> = { a: 1 };

Відображені типи можна використовувати по-різному: для створення нових, а також додавання, видалення або зміни властивостей наявних.

Найкраща практика 21: декоратори

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

Наприклад, за допомогою декоратора можна додати метод логування:

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 let originalMethod = descriptor.value;
 descriptor.value = function(…args: any[]) {
 console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
 let result = originalMethod.apply(this, args);
 console.log(`Called ${propertyKey}, result: ${result}`);
 return result;
 }
}
class Calculator {
 @logMethod
 add(x: number, y: number): number {
 return x + y;
 }
}

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

function setApiPath(path: string) {
 return function (target: any) {
 target.prototype.apiPath = path;
 }
}
@setApiPath("/users")
class UserService {
 // …
}
console.log(new UserService().apiPath); // "/users"

Висновок

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

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

Переклад статті “Mastering TypeScript: 21 Best Practices for Improved Code Quality

Що таке біткоїн і як він працює? Пояснюємо простими словами.

Біткоїн

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

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

Для розрахунків у мережі біткоїну використовується однойменна одиниця «біткоїн» (тикер BTC) — перша та найвідоміша криптовалюта. З моменту створення біткоін залишається найбільшою криптовалютою щодо ринкової капіталізації.

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

Головна інновація біткоїну

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

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

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

Мережа біткоїна складається з вузлів (нод) – пов’язаних в єдину мережу комп’ютерів із встановленим на них спеціальним програмним забезпеченням. Кожна нода зберігає та оновлює копію блокчейна біткоїну. Процес підтвердження транзакцій, створення нових блоків та валідація єдиної версії блокчейна відбувається за допомогою алгоритму консенсусу під назвою Proof-of-Work.

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

Роль та значення криптовалюти

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

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

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

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

Як «відкрити рахунок» у біткоїні та робити переклади

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

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

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

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

Після створення налаштування біткоїн-гаманця та створення адреси потрібно купити перші біткоїни.

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

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

Хто вигадав біткоїн і хто розвиває його зараз

Концепція біткоїну була вперше описана у технічному документі (whitepaper), опублікованому 31 жовтня 2008 року. Його автором був хтось під псевдонімом «Сатоші Накамото», проте досі невідомо, хто саме ховається за цим ім’ям — це людина чи група розробників. Всі повідомлення, публікації та інші «цифрові сліди» Накамото можна почитати на сайті Satoshi Nakamoto Institute (англійською).

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

З середини 2010 року Накамото перестав брати участь у розвитку біткоїну. Після цього за подальшу розробку та координацію функціонування мережі тепер відповідає спільнота розробників. Конкретна пропозиція щодо покращення коду біткоїну називається Bitcoin Improvement Proposal (BIP).

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

Як змінювалася ціна біткоїну

При запуску блокчейну біткоїну в 2009 році вартість першої криптовалюти дорівнювала нулю. До травня 2010 року його вартість зросла до 10 центів. До речі, щороку 22 травня криптоспільство відзначає Bitcoin Pizza Day: цього дня у 2010 році відбулася знаменита купівля піци за 10 000 BTC.

У квітні 2011 року 1 BTC коштував $1, але вже за кілька місяців, 7 червня, його ціна досягла майже $30. Потім вартість біткоїну почала падати, знизившись до $2 до середини листопада 2011-го. 2012 рік був небагатий на події, але у 2013-му сталося перше «бичаче ралі»: якщо на початку року за 1 BTC давали трохи більше $13, то до грудня його ціна злетіла до рекордного рівня $1237, потім відкотилася до $700. Протягом 2014 року ціна біткоїну поступово знижувалась, досягнувши $315 до початку 2015 року.

Як змінювалася ціна біткоїну

Друге масштабне ралі біткоїну сталося у 2017 році. Якщо на початку року 1 BTC торгувався на рівні $1000, то починаючи з квітня його курс почав різко зростати: спочатку до $2500 на початок червня, потім вище $4000 у серпні та $7000 у жовтні. Наприкінці 2017-го та на початку 2018-го вартість першої криптовалюти на деяких біржах практично досягла позначки $20000. Однак досить швидко ціна біткоїну відкотилася спочатку до $10000 і продовжувала знижуватись протягом 2018 року. До грудня курс біткоїну досяг дна на позначці трохи вище за $3200. Після цього почалося на ринку криптовалют, почалося відновлення.

До кінця червня 2019 року за 1 BTC давали майже $12000. Після ще кілька локальних падінь, які змінював висхідний тренд, біткоїн знову підріс до $12000 до жовтня 2020 року.

Після цього почалося нове бичаче ралі, яке призвело до першої криптовалюти на нову вершину — $63000 — до квітня 2021 року. Через три місяці біткоїн втратив майже 50% вартості, проте потім його ціна знову почала зростати. У результаті 9 листопада 2021 року біткоїн досяг нового піку на рівні $69000. Потім розпочалася корекція з короткими періодами відновлення наприкінці березня 2022 року.

Глобальна нестабільність в економіці та політиці вдарили по фінансовому ринку і негативно позначилися на ціні першої криптовалюти, яка відкотилася приблизно до $20 000 до осені 2022 року.

Скільки всього біткоїнів і звідки беруться нові монети

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

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

Появу нових біткоїнів можна порівняти з емісією грошей, тільки замість державних структур, які друкують нові банкноти, криптовалюту виробляють самі користувачі. Цей процес називається «майнінг». Він побудований вирішенні комп’ютерами складних математичних завдань. Комп’ютери при цьому знаходяться в різних точках планети, а майнери для більшої ефективності роботи об’єднуються в пули. За свою роботу вони одержують певну нагороду.

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

Чи правда, що біткоїн — фінансова піраміда

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

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

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

З чого складається цінність біткоїну

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

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

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

Кажуть, що біткоін анонімен. Це так?

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

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

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

Джерело

Як працює алгоритм консенсусу Proof-of-Stake (PoS) і чому він такий популярний?

Proof-of-Stake (PoS)

  • Proof-of-Stake (PoS) — найпопулярніший алгоритм консенсусу в блокчейні, на основі якого побудовано багато криптовалют і блокчейн-платформ, наприклад Ethereum, Cardano, Solana, Tezos і Algorand.
  • Популярність PoS обумовлена ​​відсутністю необхідності купувати дороге обладнання для майнінгу та можливістю легкого пасивного заробітку через стейкінг криптовалют .
  • Перевагою Proof-of-Stake перед іншим популярним алгоритмом Proof-of-Work (PoW) є мале споживання енергії для генерації блоків та забезпечення безпеки блокчейну;

Чому і як виник Proof-of-Stake?

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

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

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

Автор біткоїна Сатоші Накамото в жовтні 2008 року в white paper першої криптовалюти запропонував механізм Proof-of-Work («доказ роботи»).

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

Вже через кілька років після запуску біткоїну стало зрозуміло, що принцип роботи Proof-of-Work призводить до постійного зростання майнінгової потужності, а отже, і витрат на електроенергію. Крім того, через необхідність використання потужного обладнання знижувалась доступність майнінгу.

11 липня 2011 року на популярному тоді форумі про криптовалюти Bitcointalk було запропоновано ідею альтернативного механізму консенсусу для біткоїну, який отримав назву Proof-of-Stake, або «доказ частки володіння».

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

Вже у серпні 2012 року цей новий механізм консенсусу отримав перше практичне втілення у криптовалюті PPCoin. Нові монети розподіляли через майнінг, а транзакції могла обробляти будь-яка нода, що зберігала криптовалюту PPC. Та ж гібридна схема консенсусу використовувалася і в інших ранніх PoS-проектах, наприклад Gridcoin і Blackcoin. Першою «чистою» PoS-криптовалютою без майнінгу став блокчейн Nxt, запущений 24 листопада 2013 року.

Механізм консенсусу Proof-of-Stakе виявився настільки вдалим і гнучким, що в наступні роки його впровадили в сотнях криптовалют у різних варіантах та модифікаціях.

Принцип роботи Proof-of-Stake

Відповідно до початкової концепції Proof-of-Stakе, право на управління блокчейном надається всім його учасникам відповідно до частки монет, якими вони володіють.

Наприклад, у криптовалюті Nxt з її «канонічним» механізмом PoS шанс сформувати черговий блок є у всіх користувачів, які мають в офіційному гаманці NXT Client щонайменше 1002 NXT протягом останніх 1440 блоків. При цьому кожен гаманець фактично є повним вузлом (нодою) та зберігає власну копію блокчейну. Такий гаманець може бути запущений на високопродуктивному сервері, так і на ноутбуці, мікрокомп’ютері Raspberry Pi і навіть у хмарному сервісі.

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

Процес створення блоків у Nxt та інших ранніх PoS-криптовалютах отримав назву «форджинг» (тобто «ковка»), проте на цей час цей термін застосовується рідко.

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

Як делегування монет вплинуло на продуктивність PoS

Використання механізму Proof-of-Stakе, коли виробником блоків може практично будь-який власник криптовалюти, дозволяє досягти високого рівня децентралізації та безпеки блокчейну. Однак, згідно з трилемою блокчейна, при цьому доводиться жертвувати продуктивністю. У згаданій мережі криптовалюти Nxt пропускна спроможність становить лише 4 транзакції в секунду, що помітно нижче, ніж у багатьох криптовалют, які використовують консенсус PoW. Наприклад, Dogecoin обробляє 33 транзакції за секунду.

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

У 2013 році Деніел Ларімер , американський програміст і криптопідприємець, використав цю концепцію для створення механізму Delegated Proof-of-Stake (DPoS). Вперше він був реалізований у блокчейн-платформі BitShares, а потім у різних варіантах втілений у найвідоміших криптопроектах EOS, Cardano, Tezos тощо. Сьогодні функція делегування стала галузевим стандартом та використовується практично у всіх імплементаціях PoS.

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

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

  • Polkadot – до 16;
  • BNB Chain та EOS – 21;
  • Near – 100;
  • Cardano – близько 3200;
  • Avalanche – близько 1200;
  • Solana – понад 3400.
  • Ethereum – понад 400 тисяч.

Як правило, для запуску валідатора потрібне спеціальне обладнання з постійним доступом до Інтернету, а також значна сума нативних монет мережі. Наприклад, валідатор у мережі Ethereum повинен мати щонайменше 32 ETH, а валідатор Tezos — щонайменше 8000 XTZ.

Proof-of-Stake та стейкінг

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

Наприклад, у блокчейн-платформі Tron суперпредставник (так в даному випадку називається валідатор), який згенерував черговий блок і обробив транзакції, отримує 32 ТRX. Частиною цієї суми він ділиться з користувачами, які поклали свої TRX у стейкінг та таким чином проголосували за нього.

Прибутковість стейкінгу для валідаторів та власників монет визначається двома факторами:

  • швидкістю емісії, що визначається фіксованим значенням монет, що видаються на кожен новий блок;
  • часткою монет у обігу, які заблоковані в стейкінгу (Staking Ratio);

Наприклад, якщо в рік через стейкінг випускають 1 млн. монет при загальній пропозиції 100 млн. монет, то прибутковість стейкінгу при 50% заблокованих монет складе 2% річних. Якщо ж у стейкінгу заблоковано 25% пропозиції, то доходність збільшується вдвічі, до 4% річних.

Які різновиди Proof-of-Stake існують

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

Наведемо деякі з них:

  • Leased Proof-of-Stake (LPoS , «орендований доказ частки»)  використовується в блокчейні Waves, де користувачі за винагороду здають свої монети в оренду валідатору;
  • Nominated Proof-of-Stake (NPoS , «номінований доказ частки»)  використовується в блокчейн-платформі Polkadot і передбачає наявність так званих номінаторів, які вносять застави за валідаторів та відповідають за їхню сумлінність;
  • Pure Proof-of-Stake (PPoS, “чистий доказ частки”) – застосовується в мережі Algorand, де валідатори наступного блоку таємно і випадково вибираються серед усіх гаманців з балансом більше 1 ALGO;
  • Effective Proof-of-Stake (EPoS, “ефективний доказ частки”)  застосовується в блокчейн-платформі Harmony. Має особливий механізм розподілу винагород, що заохочує запуск безлічі дрібних валідаторів замість малої кількості великих, що стимулює децентралізацію;
  • Proof-of-Authority (PoA)  гібридний алгоритм, який поєднує доказ частки та репутацію валідаторів, кожен із яких має бути схвалений розробниками. В PoA валідатор повинен проходити процедуру верифікації особи, схожу на KYC. Цей алгоритм використовує BNB Chain.

Чи можливий перехід на Proof-of-Stake біткоїну та інших криптовалютів?

Висока енерговитратність майнінгу криптовалют, які працюють на алгоритмі PoW, уже багато років є предметом критики. Згідно з недавніми дослідженнями Кембриджського центру альтернативних фінансів, майнінг біткоїну відповідальний за викид 0,1% всього антропогенного вуглекислого газу.

Саме цей чинник став одним із головних аргументів при спробах заборони майнінгу у різних країнах. Так, до кінця 2021 майнінг криптовалют заборонили в Китаї. У березні 2022 року Європарламент виніс на голосування питання щодо заборони криптовалюту. Хоча законопроект не підтримали, він наголосив на тенденції до видавлювання PoW з легального поля.

Після успішного переходу мережі Ethereum на консенсус Proof-of-Stake 15 вересня 2022 енергоспоживання мережі знизилося майже в 2000 разів або на 99,95%. У зв’язку з цим із новою силою розгорнулося обговорення переходу популярних PoW-криптовалютів на PoS.

Ще в грудні 2021 року розробники мем-криптовалюти Dogecoin оголосили про швидкий її перехід на алгоритм Proof-of-StakeДопомагати їм у цьому процесі вирішив Віталік Бутерін, співзасновник Ethereum.

Компанія Electric Coin Company, розробник анонімної криптовалюти Zcash, також обговорює із співтовариством перспективи переходу на PoS. За словами засновника компанії Зуко Вілкокса, це не лише підвищить безпеку та енергоефективність блокчейну, а й допоможе залучити власників ZEС до управління протоколом.

Найбільші сумніви викликає можливість переходу на PoS у разі біткоїну.

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

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

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

Джерело

П’ять шаблонів завантаження даних для підвищення швидкодії сайтів

підвищення швидкодії сайтів

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

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

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

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

Перебір зі спіннерами завантаження та водоспад даних

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

Наочно подивитися це можна, відкривши браузер і зазирнувши у вкладку Network інструментів розробника на прикладі Medium.

Домашня сторінка Medium

Тут у нас два важливі компоненти:

  1. Графік показує тимчасову лінію кожного запрошеного і завантаженого файла. Тут ви бачите, які файли йдуть першими, і можете простежити кожен послідовний запит до моменту, коли завантаження файлу займає тривалий час. Далі цей файл можна проінспектувати та з’ясувати, чи є можливість його оптимізувати.
  2. Внизу сторінки можна перевірити, скільки КБ ресурсів споживає ваш клієнт. Важливо, скільки даних клієнту необхідно завантажувати. При першій спробі цю інформацію можна використовувати як бенчмарк для подальших оптимізації.

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

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

Але хіба очікування даних не є зрозумілим?

Так, але їхнє завантаження можна прискорити.

Loading

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

  • даних від API бекенда;
  • створення макета згідно з цими даними.

Спочатку ви здійснюєте асинхронний виклик до API, потім отримуєте URL-адресу ресурсу в CDN і тільки після цього можете почати створювати макет на клієнтській стороні. Це чимало роботи, коли потрібно показати ваше обличчя, ім’я, статус та пости Instagram відразу.

П’ять важливих шаблонів завантаження даних

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

Ви тут щоб оптимізувати. Пам’ятайте, що менше, тим краще.

Але якщо можливості зменшити обсяг даних у вас немає? Як забезпечити високу швидкість виконання коду? Що ж, тут вам допоможуть п’ять корисних шаблонів завантаження даних, які дозволять прискорити роботу сайту.

Рендеринг на стороні сервера та Jamstack

У сучасних JS-фреймворках відображення сторінок найчастіше реалізується на клієнтській стороні (CSR). Браузер отримує у вигляді корисного навантаження JS-бандл та статичний HTML, після чого малює DOM, а також для реактивності додає слухачів та активатори подій. Коли така програма відображається в DOM, сторінка блокується до завершення цього процесу. Рендеринг робить програму реактивною. Для його виконання необхідно зробити ще один виклик API до сервера та вийняти всі необхідні дані.

У свою чергу, малювання на стороні сервера (SSR) передбачає передачу програмою простого HTML клієнту. SSR можна розділити на два типи: з гідратацією та без гідратації. SSR – це стара техніка, яка використовується такими фреймворками, як WordPress, Ruby on Rails та ASP.NET. Основне її завдання – надати користувачеві статичний HTML із необхідними даними. На відміну від CSR, SSR не вимагає додаткового виклику API до сервера, оскільки саме сервер генерує шаблон HTML і завантажує дані.

У більш сучасних рішеннях на зразок Next.js використовується гідратація, коли статичний HTML гідратується на клієнтській стороні за допомогою JS. Можна порівняти це із заварюванням швидкорозчинної кави: кавовий порошок – це HTML, а вода – це JS. При змішуванні порошку з водою ви, очевидно, отримаєте каву.

Але що таке Jamstack? Jamstack аналогічний SSR, оскільки клієнт отримує простий HTML. Але під час SSR клієнт отримує HTML з сервера, а у випадку з Jamstack програми передають заздалегідь згенерований HTML прямо з CDN. У зв’язку з цим вони зазвичай завантажуються швидше, але тут розробникам складніше створювати динамічний контент. Такі програми рахунок попередньої генерації HTML хороші клієнта. Однак при використанні великого обсягу JS-коду на стороні клієнта стає все складніше виправдати використання Jamstack замість СSR.

І SSR, і Jamstack мають особливості. Спільне з-поміж них те, що вони не навантажують клієнта рендерингом всієї сторінки, використовуючи JS.

Jamstack у порівнянні з SSR та CSR

Коли ви оптимізуєте SEO свого сайту, рекомендується використовувати SSR або Jamstack, оскільки порівняно з CSR ці техніки повертають HTML-файли, зручні для перегляду пошуковими роботами. Пошуковики, звичайно, також можуть переглядати та компілювати JS-файли для CSR. Хоча відображення кожного JS-файлу в додатку з CSR може бути витратним за часом і знижувати ефективність SEO вашого сайту.

SSR і Jamstack дуже популярні, і все більше проектів переходять на кадри для SSR на кшталт Next.js і Nuxt.js, відмовляючись від їх ванільних CSR-аналогів – React і Vue. Головна причина в тому, що SSR-фреймворки забезпечують більшу гнучкість щодо SEO. У Next.js є цілий розділ, орієнтований на SEO оптимізації.

Додаток з SSR зазвичай містить шаблонизатори, які впроваджують змінні HTML під час його передачі клієнту. Наприклад, у Next.js можна завантажити список студентів, написавши:

export default function Home({ studentList }) {
  return (
    <Layout home>
        <ul>
          {studentList.map(({ id, name, age }) => (
            <li key={id}>
              {name}
              <br />
              {age}
            </li>
          ))}
        </ul>
    </Layout>
  );
}

Jamstack популярний для сайтів документації, які зазвичай компілюють код HTML-файли і розміщують їх в CDN. Файли Jamstack зазвичай до компіляції в HTML використовують розмітку Markdown. Ось приклад:

---
author: Agustinus Theodorus
title: 'Title'
description: Description
---
Hello World

Активне кешування пам’яті

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

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

Але як робити правильний вибір між кешем Redis (серверним) та кешем браузера (локальним)? Обидва ці варіанти можна використовувати одночасно, але кожен із них буде слугувати своїй меті.

Схеми кешування

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

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

Генерація подій даних

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

Типова архітектура Websocket

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

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

Архітектура генерації подій

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

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

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

const ws = new WebSocket('ws://localhost');ws.addEventListener('message', (event) => {
    console.log('Message from server ', event.data);
});

Бажаєте реагувати на події сервера? Додайте функцію addEventListener та вставте зворотний дзвінок, який вона використовуватиме.

ws.send('Hello World');

Бажаєте надсилати повідомлення? І тут WebSocket має рішення. Надсилання повідомлень із сервера можна реалізувати за допомогою функції send. Причому це не складніше реалізації Hello World. Приклади взято з документації MDN .

Попереднє та відкладене завантаження даних

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

Попереднє завантаження

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

<link rel="prefetch" href="https://example.com/example.html">

URL для попереднього завантаження встановлюються в атрибуті relHTML-елемента link. Однак дана техніка має як плюси, так і мінус.

Плюси:

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

Мінус:

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

Відкладене завантаження

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

Відкладене завантаження

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

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

Resumability

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

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

Порівняння Resumability та гідатації. Зображення взято з Qwik

Завантаження сторінок буде блискавичним, оскільки клієнт замість перезавантаження чогось десеріалізуватиме стан, впроваджений в HTML. Resumability є досить чужорідною концепцією і багатьох проектів здасться зовсім незвичним прийомом. Придумав цей шаблон творець Qwik, Міско Хевері.

Qwik– це JS-фреймворк, що внутрішньо працює за принципом Resumability, який у нього закладався спочатку. Якщо ж говорити про такі фреймворки, як React і Vue,  то вони ніколи не зможуть задіяти техніку Resumability, не пожертвувавши зворотною сумісністю. Причина в тому, що компонент відкладеного завантаження Qwik працює асинхронно на відміну від більшості синхронно влаштованих фреймворків JavaScript.

Завдання Qwik – завантажувати якнайменше JS-коду. Справа в тому, що робити це відкладено досить важко, а в деяких випадках навіть неможливо. Чим менше вам потрібно, тим краще. Resumability дозволяє розробникам детально налаштувати відкладене завантаження та скоротити споживання пам’яті мобільними програмами, оптимізуючи під них сайт.

Використання Qwik у певному сенсі нагадує React – зокрема вони схожий синтаксис. Ось фрагмент коду Qwik. Основа програми буде представлена ​​у формі HTML:

import { App } from './app';export const Root = () => {
  return (
    <html>
      <head>
        <title>Hello Qwik</title>
      </head>
      <body>
        <App />
      </body>
    </html>
  );
};

Тут є залежність від App компонента Qwik для відкладеного завантаження:

import { component$ } from '@builder.io/qwik';
export const App = component$(() => {
  return <p>Hello Qwik</p>;
});

Qwik і React також мають схожість на рівні компонентів, а ось у серверній частині вони вже відрізняються.

import { renderToString, RenderOptions } from '@builder.io/qwik/server';
import { Root } from './root';export default function (opts: RenderOptions) {
  return renderToString(<Root />, opts);
}

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

Узагальнення

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

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

  1. Відображення на стороні сервера (SSR) та Jamstack.
  2. Активне кешування пам’яті.
  3. Генерація подій даних.
  4. Попереднє та відкладене завантаження.
  5. Resumability.

Кожен із них ефективний на своєму місці.

SSR та Jamstack зазвичай добре підходять для додатків, що вимагають меншого керування станом на стороні клієнта. З появою сучасних фреймворків на зразок React все більше людей познайомилися з патерном відмальовки на клієнтській стороні (CSR), але в результаті спільнота все ж таки повертається до SSR. Техніка SSR використовується у старих MVC фреймворках, де за допомогою шаблонізаторів на основі даних бекенда генерується HTML. Jamstack – це ще більш стара ілюстрація початкового веб-середовища, в якому використовувався лише HTML.

Активне кешування пам’яті дозволяє швидше завантажувати дані з API. Ця техніка вирішує важливу проблему завантаження даних шляхом їх кешування або на віддаленому сервері (Redis) або локально в браузері. Крім цього, вона використовується в шаблоні попереднього завантаження даних.

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

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

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

Подальші кроки

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

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

Переклад статті “Five Data-Loading Patterns To Improve Web Performance

Що таке криптовалюта простими словами?

криптовалюта

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

Коли з’явилися криптовалюти

Пропозиції щодо створення криптовалюти висловлювали ще до появи інтернету. Перші алгоритми, створені задля передачу цінності через інтернет, запропонував американський криптограф Девід Чаум. Наприкінці 80-х років він заснував відповідний проект під назвою DigiCash. Однак той закінчився невдачею.

Одним із проектів, близьким до сучасних криптовалютів, є b-money . Проект започаткував програміст Вей Дай. У b-money він уперше запропонував механізм консенсусу Proof-of-Work (PoW) для фіналізації транзакцій та розподілену базу даних для зберігання інформації про транзакції.

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

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

Криптовалюти та блокчейн: відмінності від традиційних фінансів та грошей

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

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

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

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

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

Як працює криптовалюта : ключові особливості

Блокчейн

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

Хешування

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

Алгоритм консенсусу

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

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

Адреси

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

Транзакції

Щоб перекласти криптовалюту, користувач надсилає до мережі «доручення», що містить інформацію про суму переказу та адресата. Дані перекладу автоматично верифікуються електронним підписом за допомогою приватного ключа. Після потрапляння повідомлення до мережі воно включається до одного з наступних блоків, який «добувають» ноди блокчейна. Будь-яка нода може перевірити підпис кожного перекладу, використовуючи публічний ключ. Такий алгоритм гарантує збереження коштів та неможливість здійснення транзакцій третіми особами.

Незмінність даних

Після додавання блоку в ланцюжок усі його транзакції вважаються підтвердженими та незворотними. Верифікація всіх транзакцій, зокрема минулих, відбувається кожен цикл досягнення консенсусу мережі. Недостовірні дані можуть потрапити в блокчейн, тільки якщо зловмисник зможе розшифровувати хеш блоків або контролюватиме суттєву частку всіх нод мережі. Наприклад, у разі біткоїну цей поріг становить 51%. За даними на 13 жовтня 2022 року, у всьому світі працює понад 15 000 нод , що робить атаку на біткоїн практично неможливою.

Класифікація криптовалют: монети та токени

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

Головний віртуальний криптоактив, який забезпечує функціонування ланцюжка блоків, називається нативним. Останній застосовується для оплати трансакційних комісій та виплат винагород вузлам. На платформах, які дозволяють запускати смарт-контракти, криптовалюта нативна використовується для оплати їх виконання. У біткоїну та Ethereum нативна монета носить ту саму назву, що і проект, і торгується під тикерами BTC та ETH, відповідно.

Різні криптоактиви можуть з’явитися в результаті хардфорку. До них відносяться, наприклад, Bitcoin Cash та Ethereum Classic.

Сьогодні криптовалюте вже не обов’язково потрібен окремий блокчейн: багато криптоактивів створюють для використання в різних додатках, що працюють в одній мережі. Такі криптовалюти називають токенами. Портал CryptoSlate відслідковує котирування близько 1300 токенів, випущених у мережі Ethereum.

Випуск токенів став можливим завдяки смарт-контрактам, які стали популярними після запуску Ethereum. Створити власну криптовалюту може будь-який користувач, який має навички програмування. Найбільш відомим стандартом токенів є ERC-20.

Які бувають токени

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

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

Монети можуть надавати певні права чи привілеї. Токени використовують для залучення коштів при первинній пропозиції монет (ICO). У деяких проектах віртуальні активи надають право на частину прибутку проекту. Токени, що мають набір функцій у рамках програми або мережі, також називають ютіліті-токен (utility tokens).

Криптовалюти, офіційно визнані цінними паперами, називають сек’юріті-токенами. На жовтень 2022 року є принаймні кілька десятків таких криптоактивів.

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

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

У сучасних мережах випускаються невзаємозамінні токени (NFT). Це унікальні монети, які представляють певні цифрові об’єкти (зображення, аудіо та відеофайли). NFT стали об’єктами колекціонування. Перші незамінні токени є раритетами і мають високу вартість (наприклад, CryptoPunks). Також NFT можуть виконувати інші функції, наприклад використовуватися як аватари або квитки на певні заходи.

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

Як формується цінність криптовалюти

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

Розподіл початкового випуску монет між користувачами визначається розробником. Зазвичай частина активів залишається командою проекту. Кошти резервуються на рекламу розробку та розвиток проекту. Частина монет використовується для залучення інвестицій. Їх можуть купити великі інвестори в рамках одного або кількох раундів. Багато проектів використовують публічний токенсейл для продажу віртуальних активів усім охочим. В інших випадках практикують розподіл монет між активними користувачами – аірдроп.

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

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

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

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

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

Джерело

Що таке ChatGPT чи хто такий ChatGPT?

ChatGPT

30 листопада лабораторія штучного інтелекту OpenAI представила свій чат-бот ChatGPT, що базується на нейромережевій мовній моделі GPT-3.5. Про розробку майже відразу почали писати як про саму «людську», «навчену» та «достовірну». Оптимісти передбачають, що ChatGPT замінить людину в написанні текстів та коду, песимісти жахаються, що вона замінить людину в написанні текстів та коду. І багато хто бачать у розвитку моделі ChatGPT пряму загрозу Google.

Хто такий ChatGPT?

Чат-бот, який не навчився (і не навчиться?) поганому. ChatGPT, це високорозвинений та вихований ІІ: він здатний давати вичерпні відповіді, сперечатися та відхиляти недоречні питання.

Розробники створювали ChatGPT на суперкомп’ютері Azure AI на основі мовної моделі GPT-3,5 компанії OpenAI. Чат-бот прагнули зробити простим у використанні, коректним та «людяним». Його навчали за допомогою великої кількості текстів з Інтернету та системи Reinforcement Learning from Human Feedback.

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

Варто відразу обмовитися, що при всьому прагненні бути коректним, він не застрахований від неправильних відповідей. Оглядач Bloomberg у відповідь на запит біографії Муссоліні отримав історію його захоплення скейтбордами. Журналісти Wired були збентежені порадами «як подрібнений порцеляна, додана в грудне молоко, може підтримувати травну систему немовлят» і зазначили, що чат-бот часто мислить стереотипами, яким схильне все суспільство. Stack Overflow взагалі заборонив публікувати відповіді, згенеровані ІІ, саме через те, що вони часто створювали лише ілюзію правильних рішень.

Коротку історію OpenAI та ChatGPT описав Insider

Ілон Маск та колишній президент Y Combinator Сем Альтман заснували OpenAI у 2015 році. У 2016 році компанія запустила Gym, набір інструментів для створення систем самонавчання ІІ. У тому ж році з’явилася платформа Universe.

У 2018 році, через три роки після заснування OpenAI, Маск залишив раду директорів компанії. Згідно з офіційною версією, гендиректор Tesla подав у відставку, щоб «усунути потенційний майбутній конфлікт», який міг виникнути через те, що автовиробник також зосередив свою увагу на ІІ. Пізніше Маск сказав, що пішов із компанії через незгоду з поглядами команди OpenAI на майбутній розвиток: він вважав, що некомерційний характер компанії має зробити її більш відкритою.

2019 року компанія створила чат-бот, який міг писати достовірні фейкові новини. Спочатку в OpenAI заявили, що бот настільки досяг успіху в цьому занятті, що вони вирішили не випускати його в люди. Але пізніше компанія таки анонсувала версію інструменту ІІ під назвою GPT-2. 2020 року з’явився GPT-3.

Про своє партнерство з Microsoft OpenAI оголосила наприкінці 2019 року. Незадовго до того компанія відмовилася від некомерційного статусу. Інвестиції Microsoft становили $1 млрд, і вона разом із OpenAI ставала володаркою ексклюзивної ліцензії на всі розробки.

Торік з’явилося ще одне дітище – Dall-E – ІІ для створення зображень.

Після виходу ChatGPT Ілон Маск назвав його «жахливо добрим», впевнений, що «ми недалеко від небезпечно сильного ІІ».

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

Що OpenAI збирається робити далі

У планах OpenAI зробити ChatGPT доступним у вигляді інтерфейсу прикладного програмування, щоб сторонні розробники інтегрували його у свої сайти або додатки без необхідності розбиратися в базовій технології.

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

Він нас погубить: погляд скептиків

Є побоювання, що ChatGPT поставить під удар систему освіти. Якщо ІІ непогано виглядає як помічник у процесах навчання, то на етапі контролю та оцінки результатів він може нашкодити: ніколи ще студентські реферати не виглядали такими унікальними та «рукотворними». Techcrunch цитує Пола Кедроскі з Массачусетського технологічного: “Ганьба OpenAI за запуск цієї кишенькової ядерної бомби без обмежень у непідготовлене суспільство”.

Він упевнений, що це той випадок, коли технології можуть проковтнути творців. «Хтось може сказати: «Те саме відбувалося, коли на автомобільні заводи прийшла автоматизація і робітники залишилися без роботи?». Але це зовсім інше. Ці специфічні технології навчання самокаталізують; вони навчаються на запитах. Таким чином, роботи на виробничому підприємстві, хоч і руйнували і створювали неймовірні економічні наслідки для людей, які там працюють, не розгорнулися і не почали поглинати все, що відбувається всередині заводу, переміщаючись по секторах. Хоча це те, що ви повинні очікувати (ІІ).»

The Verge бачить причини бути обережними з ChatGPT у «надлишку можливостей»: моделі ІІ – це «чорні ящики», вони настільки величезні та складні, що ми не до кінця розуміємо, як вони працюють, не робимо всіх висновків і не прораховуємо всіх варіантів розвитку та всіх майбутніх наслідків.

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

Загроза Google

Те, в якому вигляді ChatGPT видає відповіді на питання, має напружити Корпорацію добра. Оглядач Techcrunch упевнений, що розробка OpenAI підібралася найближче до того, як має працювати пошукач.

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

Але Google навряд чи піде шляхом генерації власних унікальних відповідей на питання, тому що це зламає його бізнес-модель: згідно з даними, зібраними тим самим Bloomberg, близько 81% виручки Alphabet Inc. у розмірі $257,6 млрд у 2021 році припадало на рекламу, більша частина якої була з оплатою за клік.

З одного боку, нехай розквітають усі квіти, але сам автор публікації визнається, що в 13 із 18 пошукових запитів ChatGPT виявився суб’єктивно кращим і кориснішим, ніж Google. При цьому Microsoft має свій пошуковик, Bing, і вона точно не пропустить привабливої ​​можливості інтегрувати в нього ІІ.

Трохи прикладної культурології

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

Yahoo розглянув схожий сюжет в історії з ChatGPT, коли технології будуть здатні замінити людину, і він може не знайти собі місця у майбутньому.

Джерело

Що таке автоматичний маркет-мейкер?

маркетмейкер

Автоматичний маркет-мейкер (Automated Market Maker, AMM) – програмний алгоритм контролю ліквідності та ціноутворення криптоактивів на децентралізованих біржах.

AMM-системи широко застосовуються у сфері DeFi, зокрема на децентралізованих біржах (DEX), таких як включаючи Uniswap, Balancer, Bancor та Curve.

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

Як з’явився автоматичний маркет-мейкер (АММ)?

Автоматичний маркет-мейкер (AMM) – автономний торговельний механізм, на якому працює більшість торгових DeFi-протоколів . Він забезпечує рух капіталу та виконання обмінних операцій користувачів на доступних ринках криптоактивів.

Першим відомим розробником, хто заговорив про реалізацію AMM, був член команди проекту Gnosis Алан Лю. Його ідеї були викладені засновником Ethereum Віталіком Бутеріним на Reddit у 2016 році та в особистому блозі у липні 2017 року.

Ця концепція стала основою протоколу платформи Uniswap, яка отримала перший грант у розмірі $100 000 від Ethereum Foundation. Окрім того, Віталік Бутерін консультував розробників Uniswap.

Згодом АММ став широко відомим саме завдяки Uniswap. Водночас однією з перших успішних реалізацій AMM можна назвати платформу Bancor Network, яка зібрала $140 млн через ICO у червні 2017 року.

Як пули ліквідності пов’язані з АММ?

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

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

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

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

Перші DEX-біржі працювали на Ethereum та використовували для торгівлі стандартну книгу ордерів, що застосовуються на централізованих біржах. Щоб такий торговельний механізм був ефективним, він повинен мати дуже високу швидкість обробки транзакцій. Враховуючи, що операції на децентралізованій біржі підтверджуються через блокчейн, реальна швидкість та можливості таких DEX були вкрай обмежені. АММ дозволила знайти вирішення цієї проблеми.

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

Як працюють пули ліквідності?

У пулах можна використовувати два і більше активів. Наприклад, на біржі Uniswap можна створювати пули для парних токенів. Платформа Balancer дозволяє створювати пули для трьох та більше токенів. А протокол Curve призначений для пулів на основі активів з аналогічною вартістю, таких як ETH та обернутий токен WETH або USDC та DAI . Роботу цих пулів регулює АММ.

Кожна з таких AMM-DEX може використовувати власні формули та правила для взаємодії з пулами ліквідності. Наприклад, протокол Uniswap використовує таку формулу: x * y = k.

У рівнянні x та y представляють кількість токенів, доступних у пулі ліквідності; k – Постійна величина, звана інваріантом. У випадку з Curve використовуються формули x * y = k та x + y = k.

За формулою x * y = k також працюють проекти SushiSwap та PancakeSwap – це найпоширеніший вид AMM-DEX.

Як відбувається ціноутворення активу у пулі ліквідності?

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

LP-токени – це переданий криптоактив, який можна продати або обміняти на відкритому ринку, а також інвестувати в сторонні програми DeFi.

Процес обміну одного активу в інший у вигляді пулу ліквідності називається свопом (swap). Суть процесу полягає в додаванні в пул тільки одного активу замість двох, як у випадку з LP. За проведення свопу пул стягує невелику комісію, яку можна порівняти з комісією за угоду на централізованій біржі, тобто 0,1-0,3%. Комісії розподіляються між постачальниками ліквідності пропорційно їх частці в пулі.

Приклад

Створимо умовний пул ліквідності для пари ETH/USDC. За ціною 1 ETH, яка дорівнює 2000 USDC, потрібно буде одночасно заблокувати в смарт-контракті будь-яку кількість цих двох монет у співвідношенні 1:2000. За такої ціни в пулі може бути 100 ETH та 200 000 USDC.

Баланс активів у пулі визначається їхньою ціною. Коли користувач вирішить обміняти 10 ETH, використовуючи вищеописаний пул, він внесе свої монети до смарт-контракту звичайною транзакцією. В обмін на свої 10 ETH він отримає 20000 USDC (без урахування комісії за обмін).

Після цього обміну баланс пулу складе вже 110 ETH та 180 000 USDC. Отже, вартість ETH у цьому пулі складе близько 1636 USDC замість 2000 USDC інших ринках. Така ситуація залучає арбітражних трейдерів, які отримують вигоду з дисбалансу шляхом додавання USDC до пулу, поки ціна не досягне ринкових 2 000 USDC за 1 ETH.

У чому недоліки АММ?

Хоча АММ став проривом для трейдингу та DeFi, він має ряд явних недоліків. По-перше, при здійсненні свопів за допомогою АММ існує високий ризик прослизання ціни. У свою чергу, це призводить до ризиків непостійних збитків для LP і майнером цінності (MEV) для звичайних користувачів.

Для захисту від подібних ризиків створюються й інші види AMM, такі як проект CowSwap , що поєднує розробки AMM-Balancer і протоколу Gnosis.

По-друге, на відміну централізованих бірж під час торгівлі через АММ можна розміщувати лише одне вид заявок. Торгувати лімітними заявками чи іншими видами, наприклад, Stop Loss, не можна.

Що таке непостійні збитки (Impermanent Loss)?

Непостійні збитки (Impermanent Loss, IL) – це тимчасові або нереалізовані збитки при утриманні активів у пулі ліквідності під час використання AMM-DEX. IL відносяться до постачальників ліквідності (LP), означаючи різницю в ціні на момент блокування токенів у пулі та фактичною ціною на момент утримання. Збитки називаються нереалізованими тому, що не фіксуються доти, доки ліквідність не виведена з пулу.

Як приклад візьмемо пул ліквідності на біржі Uniswap, що працює за класичною формулою x * y = k:

  1. Постачальник ліквідності заблокував 1 ETH та 2000 DAI. Його частка у пулі становила 10%.
  2. Всього в пулі знаходиться 10 ETH та 20 000 DAI – еквівалент 40 000 DAI.
  3. Баланс пулу не змінювався, оскільки не було нових постачальників ліквідності.
  4. Допустимо, що ринкова ціна ETH змінилася до 4000 DAI.
  5. Тоді арбітражні трейдери скористалися ситуацією та змінили співвідношення в пулі на 5 ETH з одного боку та на 20 000 DAI з іншого. При цьому загальний розмір пулу залишився початковим – 40 000 DAI.
  6. У цей момент постачальник ліквідності вирішив вивести свою частку з пулу – вона становить 10%.
  7. З урахуванням поточного балансу пулу він виводить 0,5 ETH та 2000 DAI, хоча спочатку додавав 1 ETH та 2000 DAI.
  8. Початкова вартість його частки була 4000 DAI (1 ETH плюс 2000 DAI) у перерахунку на стейблкоіни. Вартість активів на момент виведення склала ті ж 4000 DAI (0,5 ETH плюс 2000 DAI)
  9. Однак, якби користувач просто утримував свої 1 ETH та 2000 DAI, то вартість його активів склала б вже 6000 DAI. У цьому полягає нереалізований збиток чи прибуток під час використання AMM-DEX.
  10. Користувач також отримає 10% (пропорційно його частці в пулі) від усіх комісій пулу як нагороду за надання ліквідності. Нагорода може бути зменшена, наприклад, через податки, що відраховуються на адресу розробників або в казначейство проекту на майбутній розвиток.

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

При «спокійній» течії ринку до розрахунку непостійних збитків додають змінні рухи, як описав у своєму блозі розробник StarkNet Петериса Ерінса. У результаті прогнозних обчислень значення Impermanent Loss на дворазовому русі ціни активу становитиме близько 5,7%. Але це лише прогнозна величина — потенційні збитки передбачити дуже складно.

Як знизити ризики торгівлі за допомогою AMM?

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

Необхідно враховувати можливість руху ціни активів на обидві сторони. Деякі потенційні проблеми, такі як ризик незмінних збитків (IL), можна розрахувати заздалегідь.

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

React + TypeScript: необхідний мінімум

React + TypeScript

Чимало React-розробників запитують себе: чи треба мені вчити TypeScript? Ще як треба!

Переваги вивчення TS можуть бути зведені до наступного:

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

Ця стаття являє собою мінімальне введення з використання TypeScript у React.

Основи TypeScript, необхідні React

Примітиви

Існує 3 основні примітиви, які є фундаментом для інших типів:

string // наприклад, "Pat"
boolean // наприклад, true
number // наприклад, 23 чи 1.99

Масиви

Тип масиву складається з примітивів чи інших типів:

number[] // наприклад, [1, 2, 3]
string[] // наприклад, ["Lisa", "Pat"]
User[] // кастомний тип, наприклад, [{ name: "Pat" }, { name: "Lisa" }]

Об’єкти

Об’єкти усюди. Приклад простого об’єкта:

const user = {
  firstName: "Pat",
  age: 23,
  isNice: false,
  role: "CTO",
  skills: ["HTML", "CSS", "jQuery"]
}

Тип, що описує цей об’єкт, виглядає так:

type User = {
  firstName: string;
  age: number;
  isNice: boolean;
  role: string;
  skills: string[];
}

Припустимо, що користувач має друзів, які також є користувачами:

type User = {
  // ...
  friends: User[];
}

Пет завжди ставив кар’єру на перше місце, тому в нього цілком може не бути друзів:

const user = {
  firstName: "Pat",
  age: 23,
  isNice: false,
  role: "CTO",
  skills: ["CSS", "HTML", "jQuery"],
  friends: undefined,
}

У TypeScript для позначення опціонального (необов’язкового) поля використовується символ ? після назви поля:

type User = {
  // ...
  friends?: User[];
}

Перерахування

Ми визначили тип поля role як string:

type User = {
  // ...
  role: string;
}

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

Для цього відмінно підійде перерахування (enumeration, enum):

enum UserRole {
  CEO,
  CTO,
  SUBORDINATE,
}

Так набагато краще. Але Пет знає, що значення елементів перерахування є числа. Значенням CEOє 0CTO– 1, а SUBORDINATE– 2. Пету це не до вподоби.

На щастя, як значення елементів перерахування можна використовувати рядки:

enum UserRole {
  CEO = "ceo",
  CTO = "cto",
  SUBORDINATE = "inferior-person",
}

Тепер Пет задоволений:

enum UserRole {
  CEO = "ceo",
  CTO = "cto",
  SUBORDINATE = "inferior-person",
}

type User = {
  firstName: string;
  age: number;
  isNice: boolean;
  role: UserRole;
  skills: string[];
  friends?: User[];
}

const user = {
  firstName: "Pat",
  age: 23,
  isNice: false,
  role: UserRole.CTO, // равняется "cto"
  skills: ["HTML", "CSS", "jQuery"],
}

Функції

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

Типізація параметрів функції

Існує, як мінімум, 3 способи ідентифікувати звільнену особу. По-перше, ми можемо використовувати декілька параметрів:

function fireUser(firstName: string, age: number, isNice: boolean) {
  // ...
}

// чи так
const fireUser = (firstName: string, age: number, isNice: boolean) => {
  // ...
}

По-друге, ми можемо обернути параметри в об’єкт і визначити типи в об’єкті:

function fireUser({ firstName, age, isNice }: {
  firstName: string;
  age: number;
  isNice: boolean;
}) {
  // ...
}

Нарешті ми можемо винести параметри в окремий тип. Спойлер: така техніка часто використовується для проповнення компонентів React:

type User = {
  firstName: string;
  age: number;
  role: UserRole;
}

function fireUser({ firstName, age, role }: User) {
  // ...
}

Типізація значення, що повертається функцією

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

Для визначення типу значення, що повертається функцією, також є кілька способів. Ми можемо додати : Typeпісля закривання дужки списку параметрів функції:

function fireUser(firstName: string, age: number, role: UserRole): User {
  // ...
  return { firstName, age, role };
}

// чи так
const fireUser = (firstName: string, age: number, role: UserRole): User => {
  // ...
  return { firstName, age, role };
}

Якщо, наприклад, ми спробуємо повернути null отримаємо помилку:

null

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

Code

За наявності сумнівів у коректності TypeScript типу, що виводиться, достатньо навести курсор на назву змінної або функції (спасибі сучасним редакторам коду).

Інші речі, з якими вам доведеться мати справу

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

React і TypeScript

Коли мова заходить про використання TypeScript в React, слід пам’ятати, що компоненти і хуки React — це лише функції, а props — лише об’єкти.

Функціональні компоненти

У більшості випадків у нас немає необхідності визначати тип, який повертається компонентом:

function UserProfile() {
  return <div>If you're Pat: YOU'RE AWESOME!!</div>
}

Типом, що повертається компонентом є JSX.Element, як видно на наведеному нижче зображенні:

Code

Якщо ми спробуємо повернути з компонента не JSX, то отримаємо попередження:

Code

В даному випадку об’єкт user не є валідним JSX:

'UserProfile' cannot be used as a JSX component.
Its return type 'User' is not a valid JSX element.

Props

Як зазначалося раніше, props — це лише об’єкти:

enum UserRole {
  CEO = "ceo",
  CTO = "cto",
  SUBORDINATE = "inferior-person",
}

type UserProfileProps = {
  firstName: string;
  role: UserRole;
}

function UserProfile({ firstName, role }: UserProfileProps) {
    if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>
  }
  return <div>Hi {firstName}, you suck!</div>
}

// чи так
const UserProfile = ({ firstName, role }: UserProfileProps) => {
    if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>
  }
  return <div>Hi {firstName}, you suck!</div>
}

Зверніть увагу : при роботі над React-проектами ви, швидше за все, зустрінете багато коду, в якому використовується тип React.FC або React.FunctionComponent:

const UserProfile: React.FC<UserProfileProps>({ firstName, role }) {
  // ...
}

Використовувати ці типи більше не рекомендується .

Props-коллбеки

Як пропов компонентам часто передаються функції зворотного виклику (коллбеки):

type UserProfileProps = {
  id: string;
  firstName: string;
  role: UserRole;
  fireUser: (id: string) => void;
};

function UserProfile({ id, firstName, role, fireUser }: UserProfileProps) {
  if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>;
  }
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => fireUser(id)}>Fire this loser!</button>
    </>
  );
}

voidозначає, що функція нічого не повертає.

Дефолтні props

Як ви пам’ятаєте, ми можемо зробити поле опціональним за допомогою ?. Те саме можна робити з props:

type UserProfileProps = {
  age: number;
  role?: UserRole;
}

Опціональний props може мати значення за замовчуванням:

function UserProfile({ firstName, role = UserRole.SUBORDINATE }: UserProfileProps) {
  if (role === UserRole.CTO) {
    return <div>Hey Pat, you're AWESOME!!</div>
  }
  return <div>Hi {firstName}, you suck!</div>
}

Хуки

useState()

useState– Найпопулярніший хук React. У багатьох випадках його не слід типізувати. TypeScript здатний вивести типи значень, що повертаються useState() на основі початкового значення:

function UserProfile({ firstName, role }: UserProfileProps) {
  const [isFired, setIsFired] = useState(false);

  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => setIsFired(!isFired)}>
        {isFired ? "Oops, hire them back!" : "Fire this loser!"}
      </button>
    </>
  );
}
Code

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

Code

У деяких випадках TypeSctipt не може вивести тип значень, що повертаються useState():

// TS не знає, элементи якого типу будуть у масиву
const [names, setNames] = useState([]);

// початковим значенням є `undefined`, тому TypeScript-у невідом справжній тип
const [user, setUser] = useState();

// теж саме справедливо для `null` в якості початкового значення
const user = useState(null);
Code

useState()реалізовано за допомогою загального типу (дженерика, generic type). Ми можемо використовувати це для типізації стану:

// типом `names` є `string[]`
const [names, setNames] = useState<string[]>([]);
setNames(["Pat", "Lisa"]);

// типом user є `User | undefined`
const [user, setUser] = useState<User>();
setUser({ firstName: "Pat", age: 23, role: UserRole.CTO });
setUser(undefined);

// типом `user` є `User | null`
const [user, setUser] = useState<User | null>(null);
setUser({ firstName: "Pat", age: 23, role: UserRole.CTO });
setUser(null);

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

Хуки на замовлення

Кастомний хук – це просто функція:

function useFireUser(firstName: string) {
    const [isFired, setIsFired] = useState(false);
  const hireAndFire = () => setIsFired(!isFired);

    return {
    text: isFired ? `Oops, hire ${firstName} back!` : "Fire this loser!",
    hireAndFire,
  };
}

function UserProfile({ firstName, role }: UserProfileProps) {
  const { text, hireAndFire } = useFireUser(firstName);

  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={hireAndFire}>
        {text}
      </button>
    </>
  );
}

Події

З вбудованими обробниками подій у React працювати легко, оскільки TypeScript-у відомі типи подій:

function FireButton() {
  return (
    <button onClick={(event) => event.preventDefault()}>
      Fire this loser!
    </button>
  );
}

Але визначення оброблювача у вигляді окремої функції докорінно змінює справу:

function FireButton() {
  const onClick = (event: /* ??? */) => {
    event.preventDefault();
  };

  return (
    <button onClick={onClick}>
      Fire this loser!
    </button>
  );
}

Який тип має event? Існує 2 підходи:

  • гуглити (не рекомендується, викликає запаморочення));
  • приступити до реалізації вбудованої функції та дозволити TS вивести типи:
Code

Щасливі копіпастінг. Нам навіть не потрібно розуміти, що відбувається (більшість обробників є дженериками, як useState()).

function FireButton() {
  const onClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    event.preventDefault();
  };

  return (
    <button onClick={onClick}>
      Fire this loser!
    </button>
  );
}

Що щодо оброблювача зміни значення інпуту?

function Input() {
  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log(event.target.value);
  };

  return <input onChange={onChange} />;
}

А команда?

function Select() {
  const onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    console.log(event.target.value);
  };

  return <select onChange={onChange}>...</select>;
}

Дочірні компоненти

Композиція компонентів, від якої ми всі в захваті, передбачає передачу пропа children:

type LayoutProps = {
  children: React.ReactNode;
};

function Layout({ children }: LayoutProps) {
  return <div>{children}</div>;
}

Тип React.ReactNodeнадає більшу свободу вибору переданого значення. Він дозволяє передавати майже будь-що (крім об’єкта):

Code

Якщо як props повиннa передаватися тільки розмітка, тип childrenможна обмежити до React.ReactElementабо JSX.Element(що по суті те саме):

type LayoutProps = {
  children: React.ReactElement; // чи `JSX.Element`
};

Ці типи є набагато суворішими:

Code

Сторонні бібліотеки

Додавання типів

Сьогодні багато сторонніх бібліотек містять відповідні типи. У цьому випадку окремий пакет (з типами) не потрібно встановлювати.

Типи для великої кількості існуючих бібліотек містяться в репозиторії DefinitelyTyped на GitHub і публікуються під егідою організації @types(включаючи типи React). При встановленні пакета без типів та його імпорті отримуємо таку помилку:

Code

Копіюємо виділену команду та виконуємо її в терміналі:

npm i --save-dev @types/styled-components

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

Використання дженериків

Бібліотеки розраховані на різні випадки використання, тому вони мають бути гнучкими. Для забезпечення гнучкості типів використовуються дженерики. Ми їх уже бачили в useState():

const [names, setNames] = useState<string[]>([]);

Такий прийом дуже поширеним для сторонніх бібліотек. Приклад з Axios:

import axios from "axios"

async function fetchUser() {
  const response = await axios.get<User>("https://example.com/api/user");
  return response.data;
}
Code

Реагувати на запит:

import { useQuery } from "@tanstack/react-query";

function UserProfile() {
  // загальні типи даних і помилки
  const { data, error } = useQuery<User, Error>(["user"], () => fetchUser());

  if (error) {
    return <div>Error: {error.message}</div>;
  }
  // ...
}

Стилізовані компоненти:

import styled from "styled-components";

// загальний тип для props
const MenuItem = styled.li<{ isActive: boolean }>`
  background: ${(props) => (props.isActive ? "red" : "gray")};
`;

function Menu() {
  return (
    <ul>
      <MenuItem isActive>Menu Item 1</MenuItem>
    </ul>
  );
}

Способи усунення несправностей

Початок роботи з React & TypeScript

Створення нового React-проекту з підтримкою TypeScript – справа однієї команди. Я рекомендую використовувати шаблони Vite+React+TS або Next.js+TS:

npm create vite [project-name] --template react-ts

npx create-next-app [project-name] --ts

Виявлення правильного типу

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

Code

За наявності сумнівів щодо кількості доступних параметрів набираємо (...args) => та отримуємо відповідний масив:

Code

Вивчення типу

Найпростіший спосіб отримати список всіх доступних полів типу – використовувати автозавершення в IDE. Для цього достатньо натиснути CTRL + Пробіл (Windows) або Option + Пробіл (Mac):

Code

Щоб перейти до визначення типу, слід натиснути CTRL + Click (Windows) або CMD + Click (Mac):

Code

Читання повідомлень про помилки

Повідомлення про помилки та попередження TS є дуже інформативними, головне – навчитися їх правильно читати. Розглянемо приклад:

function Input() {
  return <input />;
}

function Form() {
  return (
    <form>
      <Input onChange={() => console.log("change")} />
    </form>
  );
}

Ось що показує TS:

Code

Що це означає? Що ще за тип IntrinsicAttributes? При роботі з бібліотеками (у тому числі з самим React) ви часто зустрічатимете дивні назви типів, на кшталт цього.

Моя порада: ігноруйте їх спочатку.

Найважливішою частиною є останній рядок:

Property 'onChange' does not exist on type...

Дивимося на визначення компонента Input:

function Input() {
  return <input />;
}

У нього немає пропа onChange. Саме це “не подобається” TypeScript.

Тепер розглянемо складніший приклад:

const MenuItem = styled.li`
  background: "red";
`;

function Menu() {
  return <MenuItem isActive>Menu Item</MenuItem>;
}
Code

Нічого собі повідомлення про помилку! Без паніки: прокручуємо в кінець повідомлення – як правило, відповідь знаходиться там:

Code

Перетини

Припустимо, що у нас є тип User, визначений в окремому файлі, наприклад types.ts:

export type User = {
  firstName: string;
  role: UserRole;
}

Він використовується для типізації пропов компонента UserProfile:

function UserProfile({ firstName, role }: User) {
  // ...
}

Який використовується в компоненті UserPage:

function UserPage() {
  const user = useFetchUser();

  return <UserProfile {...user} />;
}

Поки все добре. Але що якщо UserProfile буде приймати ще один проп – функцію fireUser?

function UserProfile({ firstName, role, fireUser }: User) {
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => fireUser({ firstName, role })}>
        Fire this loser!
      </button>
    </>
  );
}

Отримуємо помилку:

Code

Цю проблему можна вирішити за допомогою перетину (intersection type). При перетині всі поля двох типів поєднуються в один тип. Перетини створюються за допомогою символу &:

type User = {
  firstName: string;
  role: UserRole;
}

// `UserProfileProps` буде вміщувати в себе всі поля `User` та `fireUser`
type UserProfileProps = User & {
    fireUser: (user: User) => void;
}

function UserProfile({ firstName, role, fireUser }: UserProfileProps) {
  return (
    <>
      <div>Hi {firstName}, you suck!</div>
      <button onClick={() => fireUser({ firstName, role })}>
        Fire this loser!
      </button>
    </>
  );
}

Більш “чистим” способом є визначення окремих типів для компонентів, що приймаються props:

type User = {
  firstName: string;
  role: UserRole;
}

// !
type UserProfileProps = {
  user: User;
  fireUser: (user: User) => void;
}

function UserProfile({ user, onClick }: UserProfileProps) {
  return (
    <>
      <div>Hi {user.firstName}, you suck!</div>
      <button onClick={() => fireUser(user)}>
        Fire this loser!
      </button>
    </>
  );
}

Дякую за увагу та happy coding!

Переклад статті “Minimal TypeScript Crash Course For React

Як виправити проблему хука React useState, яка не встановлює початкове значення?

React useState

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

У цій статті ми розглянемо, як виправити React хук useState з встановленням початкового стану з props.

Оновлення стану під час оновлення властивості

Щоб оновити стан, коли props оновлюється, ми маємо спостерігати за значенням props за допомогою useEffect хука. Потім у useEffect зворотному виклику ми можемо викликати функцію встановлення стану, щоб оновити значення стану значенням властивості. Наприклад, ми можемо написати:

import React, { useEffect, useState } з "react";
  const Count = ({ count }) => { 
  const [num, setNum] = useState(count);
  useEffect(() => { 
    setNum(count); 
  }, [count]);
  return <p>{num}</p>; 
};
export default function App() { 
  const [count, setCount] = useState(0);
  return ( 
    <div className="App"> 
      <button onClick={() => setCount((c) => c + 1)}>increment</button> 
      <Count count={count} /> 
    </div> 
  ); 
}

У Count компоненті ми маємо useState хук зі count значенням як аргумент. Це встановлюється початкове значення count яке дорівнює num. Потім у нас є useEffect хук, який стежить за count значенням, передаючи його в масив у другому аргументі. Потім у зворотному виклику ми викликаємо, setNum щоб оновити num значення та відобразити його в операторі return під ним.

У App ми маємо стан count, створений хуком useState. Потім ми викликаємо setCount в обробнику onClick кнопки, яка оновлює значення count стану. І ми передаємо count значення як значення count props у Count компоненті.

Тепер, коли ми натискаємо кнопку збільшення, ми бачимо оновлення значення num та його останнє значення.

Висновок

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

Переклад статті “How to Fix the React useState Hook Not Setting Initial Value Problem?

Потужність декораторів 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.