Деструктурування для легшого доступу до даних

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

Чому деструктурування корисне?

У ECMAScript 5 та раніше потреба зчитати інформацію з об’єктів чи масивів в локальні змінні могла вилитись у велику кількість однакового коду. Наприклад:

let options = {
        repeat: true,
        save: false
    };

// отримання даних з об’єкту
let repeat = options.repeat,
    save = options.save;

Цей код отримує значення полів repeat та save з об’єкту options та зберігає ці дані у локальні змінні з відповідними іменами. Цей код виглядає просто, але уявіть, якби ви мали велику кількість змінних та полів, вам би довелось присвоювати всіх їх одне одному. А якщо дані були б ще й вкладеними, вам би довелось “прокопати” всю цю структуру лише для того, щоб знайти одну частинку даних.

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

Деструктурування об’єктів

Синтаксис деструктурування об’єктів використовує об’єктний літерал з лівого боку операції присвоєння. Наприклад:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type, name } = node;

console.log(type);      // "Identifier"
console.log(name);      // "foo"

У цьому коді, значення node.type зберігається у змінну type, а значення node.name у змінну name. Цей синтаксис такий самий, як і скорочення ініціалізації властивостей в об’єктних літералах, про яке ми говорили у Главі 4. Ідентифікатори type та name є оголошеннями локальних змінних та властивостями об’єкту options з яких мають бути прочитані значення.

A> Не забудьте ініціалізатор

А> При використанні деструктурування для оголошення змінних з використанням var, let або const, ви повинні вказувати ініціалізатор (значення після знаку рівності). Всі наступні рядки коду будуть кидати помилку, спричинену пропущеним ініціалізатором:

// syntax error!
var { type, name };

// syntax error!
let { type, name };

// syntax error!
const { type, name };

A> Якщо const завжди потребує ініціалізатора, навіть за умови використання недеструктуровних змінних, var та let потребують ініціалізаторів лише при використанні деструктурування.

Деструктивне присвоєння

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

let node = {
        type: "Identifier",
        name: "foo"
    },
    type = "Literal",
    name = 5;

// присвоєння інших значень через деструктурування
({ type, name } = node);

console.log(type);      // "Identifier"
console.log(name);      // "foo"

У цьому прикладі, type та name ініціалізовані зі значенням, далі дві змінні з такими ж іменами були ініціалізовані з іншими значеннями. Наступний рядок використовує деструктивне присвоєння для зміни цих значень шляхом читання з об’єкту node. Зауважте, що вам необхідно взяти вираз деструктивного присвоєння у круглі дужки. Це тому, що відкриваючі фігурні дужки сприймаються як початок блоку операторів, а блок операторів не може стояти у лівій частині присвоєння. Круглі дужки сигналізують про те, що фігурні дужки, які слідують за ними, не є блоком операторів і мають бути інтерпретовані як вираз, дозволяючи тим самим виконувати присвоєння.

Вираз деструктивного присвоєння приймає значення правої частини виразу (після =). Це означає, що ви можете використовувати деструктивне присвоєння всюди, де очікується значення. Наприклад, при передачі значення у функцію:

let node = {
        type: "Identifier",
        name: "foo"
    },
    type = "Literal",
    name = 5;

function outputInfo(value) {
    console.log(value === node);        // true
}

outputInfo({ type, name } = node);

console.log(type);      // "Identifier"
console.log(name);      // "foo"

Функція outputInfo() викликається з виразом деструктивного присвоєння. Вираз приймає значення node, оскільки воно знаходиться у правій частині виразу. Присвоєння значень в type та name поводиться так само, як і передача nodeв якості аргументу outputInfo().

W> Якщо права частина деструктивного присвоєння (вираз після =) приймає значення null або undefined, це призведе до помилки. Це станеться тому, що будь–яка спроба зчитати властивість з null або undefined призведе до помилки виконання.

Значення за замовчуванням

За використання деструктивного присвоєння, якщо ви вказуєте локальну змінну з ім’ям властивості, що не існує в об’єкті, тоді цій локальній змінній буде присвоєне значення undefined. Для прикладу:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type, name, value } = node;

console.log(type);      // "Identifier"
console.log(name);      // "foo"
console.log(value);     // undefined

Цей код задає додаткову локальну змінну value та намагається присвоїти їй значення. Однак, в об’єкті node немає відповідної властивості value, тому очікувано, що цій змінній буде присвоєне значення undefined.

За бажанням ви можете вказати значення за замовчуванням, що використовуватиметься, якщо певної властивості не існує. Щоб зробити це, скористайтесь знаком рівності (=) після ім’я властивості та вкажіть значення за замовчуванням, ось так:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type, name, value = true } = node;

console.log(type);      // "Identifier"
console.log(name);      // "foo"
console.log(value);     // true

У цьому прикладі змінній value присвоюється true в якості значення за замовчуванням. Це значення використовується лише тоді, коли властивості немає в node, або вона має значення undefined. Оскільки властивості node.value немає, змінна value використовує значення за замовчуванням. Це працює так само, як і значення за замовчуванням для функцій, які ми розглянули у Главі 3.

Присвоєння локальним змінним з іншими іменами

До цього моменту кожен приклад деструктуруючого присвоєння використовував властивості об’єктів в якості назв локальних змінних: для прикладу, значення node.type зберігалось у змінній type. Це чудово, якщо ви хочете використовувати таке ж саме ім’я, але що якщо для вас це небажано? ECMAScript 6 має розширений синтаксис, що дозволяє присвоювати значення локальним змінним з іншими іменами, і він виглядає так, як звичайний об’єктний літерал без використання скороченого запису для властивостей. Ось приклад:

let node = {
        type: "Identifier",
        name: "foo"
    };

let { type: localType, name: localName } = node;

console.log(localType);     // "Identifier"
console.log(localName);     // "foo"

Цей код використовує деструктивне присвоєння, щоб оголосити змінні localType та localName, які будуть містити значення властивостей node.type та node.name, відповідно. Запис type: localType каже, що потрібно зчитати властивість з ім’ям type та зберегти її значення у змінній localType. Такий синтаксис є ефективно протилежним до традиційного синтаксису об’єктних літералів, в яких ім’я властивості знаходиться ліворуч від двокрапки, а її значення — праворуч. У цьому випадку, ім’я змінної знаходиться праворуч від двокрапки, а поле з якого має бути зчитане значення знаходиться ліворуч.

Ви також можете додавати значення за замовчуванням у цьому випадку. Знак рівності та значення за замовчуванням, у цьому випадку, також знаходитимуться після ім’я локальної змінної. Наприклад:

let node = {
        type: "Identifier"
    };

let { type: localType, name: localName = "bar" } = node;

console.log(localType);     // "Identifier"
console.log(localName);     // "bar"

Тут, змінна localName має значення за замовчуванням "bar". Цій змінній буде присвоєно значення за замовчуванням, оскільки властивості node.name немає.

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

Деструктурування вкладених об’єктів

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

let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        }
    };

let { loc: { start }} = node;

console.log(start.line);        // 1
console.log(start.column);      // 1

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

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

let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        }
    };

// дістаємо node.loc.start
let { loc: { start: localStart }} = node;

console.log(localStart.line);   // 1
console.log(localStart.column); // 1

У цьому коді, node.loc.start зберігається у новій локальній змінній з ім’ям localStart. Шаблон деструктурування може мати довільний рівень вкладеності, з усіма можливостями, що доступні на кожному з рівнів.

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

A> Синтаксична особливість

A> Будьте обережні з використанням вкладеного деструктурування, оскільки ви можете ненароком створити оператор, що не створює жодного ефекту. Порожні фігурні дужки дозволені при деструктуруванні об’єктів, однак вони не роблять нічого. Наприклад:

// не оголошує жодної змінної!
let { loc: {} } = node;

A> Немає жодного зв’язування у цьому операторі. Оскільки справа знаходяться фігурні дужки, loc сприймається як вказівка де шукати значення, а не як змінна, яку потрібно створити. В цьому випадку, ймовірно була необхідність вказати значення за замовчуванням з використанням =, ніж використовувати :, яка вказує де шукати властивості. Цілком можливо, що такий синтаксис буде забороненим у майбутньому, проте, зараз — це синтаксична особливість.

Деструктурування масивів

Синтаксис декструктурування масивів дуже схожий на деструктурування об’єктів і відрізняється тим, що використовує синтаксис літералів масивів замість синтаксису об’єктних літералів. Деструктурування оперує позиціями в масиві замість іменованих властивостей, які доступні в об’єктах. Наприклад:

let colors = [ "red", "green", "blue" ];

let [ firstColor, secondColor ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

Тут деструктурування масиву витягує значення "red" та "green" з масиву colors і зберігає їх в змінних firstColor та secondColor. Ці значення вибрані через їхні позиції в масиві, а значення локальних змінних можуть бути будь–якими. Елементи, які не були явно вказані у шаблоні деструктурування, будуть проігноровані. Також запам'ятайте, що деструктурування не змінює масив жодним чином.

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

let colors = [ "red", "green", "blue" ];

let [ , , thirdColor ] = colors;

console.log(thirdColor);        // "blue"

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

W> Як і для випадку з об’єктами, ви обов’язково повинні забезпечити ініціалізатор за використання деструктурування масиву з var, let або const.

Деструктивне присвоєння

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

let colors = [ "red", "green", "blue" ],
    firstColor = "black",
    secondColor = "purple";

[ firstColor, secondColor ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

Деструктивне присвоєння у цьому коді працює так само, як і в передостанньому прикладі деструктурування масиву. Єдина відмінність полягає у тому, що firstColor та secondColor вже були задані. У більшості випадків, це все, що вам потрібно знати про деструктивне присвоєння з масивами, проте є ще дещо, що може бути для вас корисним.

Деструктивне присвоєння масивів можна використовувати для зміни місцями значень двох змінних. Зміна значень місцями є загальною операцією в алгоритмах сортування. В ECMAScript 5, щоб змінити місцями значення двох змінних, потрібно було вводити третю, тимчасову змінну, як у цьому прикладі:

// Зміна місцями значень у ECMAScript 5
let a = 1,
    b = 2,
    tmp;

tmp = a;
a = b;
b = tmp;

console.log(a);     // 2
console.log(b);     // 1

Тимчасова змінна tmp необхідна для зміни місцями значень a та b. Однак, з використанням деструктивного присвоєння зникає потреба в додатковій змінній. Ось як ви можете переставляти місцями значення у ECMAScript 6:

// Зміна місцями значень у ECMAScript 6
let a = 1,
    b = 2;

[ a, b ] = [ b, a ];

console.log(a);     // 2
console.log(b);     // 1

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

W> Як і у випадку з деструктуруванням об’єктів, якщо права частина виразу деструктивного присвоєння приймає значення null або undefined це призведе до помилки.

Значення за замовчуванням

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

let colors = [ "red" ];

let [ firstColor, secondColor = "green" ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

У цьому коді масив colors має лише один елемент, тому жоден елемент не відповідає secondColor. Оскільки є значення за замовчуванням для secondColor, йому встановлюється значення "green" замість undefined.

Вкладене деструктурування

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

let colors = [ "red", [ "green", "lightgreen" ], "blue" ];

// потім

let [ firstColor, [ secondColor ] ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

Тут змінна secondColor посилається на значення "green" всередині масиву colors. Цей елемент знаходиться всередині іншого масиву, тому у деструктуруючому шаблоні обов’язкові квадратні дужки довкола secondColor. Як у випадку з об’єктами, вкладеність масивів може мати довільну глибину.

Залишкові (rest) елементи

Глава 3 вводила поняття залишкових параметрів (rest–параметрів) для функцій. Деструктурування масивів має схожу концепцію — залишкові елементи або (rest–елементи). Залишкові елементи використовують синтаксис ... для присвоєння елементів, що залишились у масиві певній змінній. Ось приклад:

let colors = [ "red", "green", "blue" ];

let [ firstColor, ...restColors ] = colors;

console.log(firstColor);        // "red"
console.log(restColors.length); // 2
console.log(restColors[0]);     // "green"
console.log(restColors[1]);     // "blue"

Перший елемент colors присвоюється firstColor, а решта елементів присвоюються новому масиву restColors. Відповідно масив restColors має два елементи: "green" та "blue". Залишкові елементи корисні для того, щоб діставати з масиву певні елементи і залишати решту доступними, проте, є ще один спосіб використання.

Очевидним недоліком масивів у JavaScript є можливість їх легкого клонування. У ECMAScript 5 розробники часто використовували метод concat() у якості легкого способу клонувати масив. До прикладу:

// клонування масиву ECMAScript 5
var colors = [ "red", "green", "blue" ];
var clonedColors = colors.concat();

console.log(clonedColors);      //"[red,green,blue]"

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

// клонування масиву ECMAScript 6
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;

console.log(clonedColors);      //"[red,green,blue]"

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

W> Залишкові елементи повинні йти останніми при деструктуруванні масивів — після них не можна ставити коми. Кома після залишкових елементів призведе до синтаксичної помилки.

Змішане деструктурування

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

let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        },
        range: [0, 3]
    };

let {
    loc: { start },
    range: [ startIndex ]
} = node;

console.log(start.line);        // 1
console.log(start.column);      // 1
console.log(startIndex);        // 0

Цей код дістає node.loc.start та node.range[0] у змінні start та startIndex відповідно. Пам’ятайте, що loc: та range: у шаблоні деструктурування є лише відповідними властивостями об’єкту node. Немає такої частини node, яку не можна було би дістати з допомогою комбінування деструктурування об’єктів та масивів. Такий підхід, зокрема, зручний для отримання значень з JSON структур, без необхідності проходу по всій структурі.

Деструктивні параметри

Деструктурування має ще один корисний спосіб використання — передача аргументів у функцію. Коли функція у JavaScript приймає велику кількість параметрів, загальноприйнятим є створення об’єкту options, властивості якого задають додаткові параметри, ось так:

// властивості options є додатковими параметрами
function setCookie(name, value, options) {

    options = options || {};

    let secure = options.secure,
        path = options.path,
        domain = options.domain,
        expires = options.expires;

    // код, що встановлює cookie
}

// третій аргумент передає аргументи у опції
setCookie("type", "js", {
    secure: true,
    expires: 60000
});

Багато JavaScript–бібліотек містять функції, що схожі на функцію setCookie(). У цій функції аргументи name та value є обов’язковими, проте secure, path, domain та expires — ні. Оскільки перелік додаткових даних не важливий, ефективно було б просто мати об’єкт options з іменованими властивостями, аніж список додаткових іменованих параметрів. Такий підхід працює, але ви не можете сказати, якого вводу очікує функція, якщо просто подивитись на її оголошення — вам необхідно читати тіло функції.

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

function setCookie(name, value, { secure, path, domain, expires }) {

    // код, що встановлює cookie
}

setCookie("type", "js", {
    secure: true,
    expires: 60000
});

Така функція поводиться точно так само, як у попередньому прикладі, проте тепер третій аргумент використовує деструктурування для отримання необхідних даних. Параметри поза деструктивним параметром є чітко зрозумілими, і, в той же час, для будь—кого, хто використовує функцію setCookie(), зрозуміло, які опції вона очікує. І звісно, третій аргумент є обов’язковим, а значення, які він повинен містити, є чітко зрозумілими. Деструктивні параметри також поводяться як звичайні параметри, тобто, якщо вони не будуть передані, їх значення будуть рівні undefined.

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

Обов’язковість деструктивних параметрів

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

// Помилка!
setCookie("type", "js");

Тут третій аргумент був упущений, і тому очікувано, що він приймає значення undefined. Це спричиняє помилку, оскільки деструктивні параметри насправді є лише скороченням оголошення деструктурування. Коли викликається функція setCookie(), рушій JavaScript насправді робить таке:

function setCookie(name, value, options) {

    let { secure, path, domain, expires } = options;

    // код, що встановлює cookie
}

Деструктурування кидає помилку, коли права частина виразу приймає значення null або undefined, а саме це і стається, коли третій аргумент не передається у функцію setCookie().

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

function setCookie(name, value, { secure, path, domain, expires } = {}) {

    // ...
}

У цьому прикладі в якості значення за замовчуванням для третього параметру передається новий об’єкт. Передача цього значення за замовчуванням означає, що secure, path, domain та expires завжди будуть undefined, якщо у setCookie() не було передано третього аргументу, а тому помилка кидатись не буде.

Значення за замовчуванням для деструктивних параметрів

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

function setCookie(name, value,
    {
        secure = false,
        path = "/",
        domain = "example.com",
        expires = new Date(Date.now() + 360000000)
    } = {}
) {

    // ...
}

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

Підсумок

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

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

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

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

results matching ""

    No results matching ""