Нові фічі JavaScript, про які варто знати

JavaScript продовжує розвиватися, отримуючи нові можливості щороку через стандарт ECMAScript. Усі ці нові функції покликані спростити роботу з мовою, зробити код більш зрозумілим, ефективним та безпечним. Ця стаття розглядає цікаві нові фічі JS останніх років, які можуть допомогти вам у щоденній розробці.
Верхньорівневий await (Top-level await)
Що це таке
Раніше, якщо ви хотіли скористатися await, треба було завжди писати async-функцію. Але тепер JavaScript дозволяє використовувати await безпосередньо в модулі (ES-модулі, де використовується type=”module” чи .mjs-файли).
Приклад
const data = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(res => res.json());
console.log(data);
Чому це зручно
Не потрібно створювати обгортку з async. Наприклад, якщо ваш код має виконати кілька запитів перед виконанням іншого логічного блоку, top-level await спрощує порядок завантаження.
Опціональний ланцюжок (Optional chaining)
Що таке опціональний ланцюжок
Optional chaining – це оператор ?. у JS, що дозволяє безпечніше отримувати вкладені дані. Замість того, щоб перевіряти кожен рівень об’єкта на null чи undefined, можна використати ?. і, якщо на якомусь рівні виявиться undefined, повернеться undefined замість помилки.
Приклад
const user = { profile: { address: { city: 'Kyiv' } } };
const city = user?.profile?.address?.city;
Переваги
Код стає чистішим, менше перевірок, немає помилок типу “Cannot read property ‘address’ of undefined”.
Оператор об’єднання з null (Nullish Coalescing)
Як це працює
Оператор ?? повертає друге значення, якщо перше є null чи undefined, інакше повертає перше.
const value = someVar ?? 'default';
Відмінність від ||
Звичайний оператор || спрацьовує також при falsy-значеннях (0, “”), а nullish coalescing ?? тільки при null або undefined.
Приклад
const input = 0;
const result = input || 10;
const result2 = input ?? 10;
Метод at для масивів і рядків
Що це таке
Метод at(index) дозволяє більш гнучко отримувати елемент із масиву чи символ із рядка. Особливо зручно використовувати негативні індекси.
Приклад
const arr = [10, 20, 30, 40];
console.log(arr.at(1));
console.log(arr.at(-1));
const str = 'Hello';
console.log(str.at(-2));
Замість arr[arr.length – 1], можна писати arr.at(-1). Код стає більш читабельним.
Оператор ||= та &&=
Короткий запис операції з присвоєнням
ES пропонує декілька операторів присвоєння, які поєднують логічні оператори з присвоєнням.
Оператор ||=
obj.prop ||= defaultValue;
Еквівалентно:
if (!obj.prop) {
obj.prop = defaultValue;
}
Якщо obj.prop — falsy, тоді присвоїти defaultValue.
Оператор &&=
obj.prop &&= otherValue;
Еквівалентно:
if (obj.prop) {
obj.prop = otherValue;
}
Покращений Pattern Matching (Records and Tuples у майбутньому)
Records і Tuples є пропозицією, яка може з’явитись у JS. Це створить незмінні (immutable) структури даних із семантикою порівняння за значенням, а не за посиланням. Хоча воно ще не в офіційній специфікації, це може дозволити робити:
const record = #{ name: 'Alice', age: 20 };
const tuple = #[1, 2, 3];
Важливо стежити за цими пропозиціями, адже вони можуть значно спростити роботу зі складними структурами даних. Поки що вони у стадії пропозицій, тому треба почекати, поки браузери і Node.js втілять це нативно.
Інші дрібні, але корисні фічі
numeric separators
Дозволяють писати числа з підкресленнями для кращого читання:
const largeNumber = 1_000_000;
Intl
Багато покращень у глобальному об’єкті Intl (наприклад, Intl.RelativeTimeFormat, Intl.NumberFormat з додатковими опціями), що допомагають форматувати числа, дати та валюти.
top-level await ( ECMAScript 2022 )
У модулях з type=”module” можна використовувати await без необхідності створювати async-функцію зверху.
const data = await fetch('https://api.example.com').then(r => r.json());
console.log(data);
Висновок
JavaScript постійно розвивається, і кожна нова версія ECMAScript додає функції, що можуть спростити код і підвищити його безпеку та читабельність. Серед новинок, які вже можна використовувати в більшості середовищ: optional chaining, nullish coalescing, метод at для масивів та рядків, а також удосконалені оператори присвоєння. У перспективі варто стежити за records/tuples, які можуть відкрити нову еру у роботі з незмінними структурами даних у JS.
Щоб упевнитися, що можна застосовувати ці фічі, перевіряйте таблиці сумісності (наприклад, caniuse.com) або налаштуйте Babel/TypeScript для транспіляції, якщо потрібно підтримувати старіші браузери. Як результат, ваш код стане коротшим, більш зрозумілим і менш схильним до помилок.
Як боротися із зайвими рендерами у React

React робить створення інтерактивних інтерфейсів простішим завдяки своїй декларативній природі та оновленню тільки тих частин UI, що змінилися. Проте іноді додатки можуть відчувати проблеми з продуктивністю, коли відбувається занадто багато рендерів (re-renders). У цій статті ми розглянемо, чому це трапляється, як помітити надлишкові рендери та які підходи можуть знизити їх кількість.
Що таке зайві рендери
Зайві (або повторні) рендери — це ситуації, коли компонент React перерисовується без реальної потреби. Наприклад, коли стан або пропси компонента не змінились, але компонент все одно викликає логіку рендеру (і потенційно рендерить усіх своїх нащадків). У невеликих додатках це може бути не дуже критично, проте у великих застосунках з великою кількістю компонентів — збільшує час відгуку та знижує продуктивність.
Причини зайвих рендерів
Неправильне використання контексту
Якщо контекст (React Context) містить багато даних, то кожна зміна в ньому тригерить повторний рендер усіх компонентів, які його споживають, навіть якщо їм не потрібна зміна.
Анонімні функції та об’єкти у пропсах
Якщо в пропсах передаються нові об’єкти (наприклад, {}
) чи анонімні стрілкові функції, React вважає, що пропс змінився, і рендер відбувається знову.
Використання нових посилань у стані
Якщо стан компонента зберігає об’єкти або масиви, і при кожному оновленні створюються нові копії (навіть якщо вони логічно не змінилися), це може спровокувати повторний рендер.
Неоптимальне використання memo, useMemo, useCallback
Якщо компоненти не мемоізуються або хибно мемоізуються, React може рендерити навіть тоді, коли дані не змінилися.
Як виявити зайві рендери
Консольне логування
Простий спосіб — поставити console.log('render component')
у тілі компонента чи використати React DevTools, щоб бачити, які компоненти повторно рендеряться.
React DevTools Profiler
Забезпечує графічний інтерфейс для аналізу, які компоненти і коли рендеряться, скільки часу займають.
Плагіни (наприклад, why-did-you-render)
Існують бібліотеки, що можуть “підказати”, чому саме компонент повторно рендериться.
Підходи до мінімізації зайвих рендерів
memo, React.memo і PureComponent
React.memo (для функціональних компонентів) або PureComponent (для класових) — це механізми, що виконують поверхневе порівняння пропсів. Якщо пропси не змінились, компонент не рендеритиметься вдруге.
Приклад із React.memo:
const Child = React.memo(function Child({ data }) {
console.log('Child render');
return <div>{data.value}</div>;
});
У цьому випадку, якщо data
(як посилання) не змінилося, Child не буде рендеритись.
useCallback та useMemo
Якщо у батьківському компоненті під час кожного рендеру створювати анонімну функцію в пропсах, то дочірній компонент буде вважати, що пропс змінився. Щоб запобігти цьому, варто використовувати useCallback і useMemo.
Приклад:
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<Child onClick={handleClick} />
);
}
Тепер onClick
буде завжди тим самим посиланням, і якщо інші пропси не змінюються, Child не рендериться повторно.
Оптимізація контексту
Якщо контекст містить занадто багато даних, кожна зміна змушуватиме всі компоненти, що його використовують, рендеритись. Краще робити контекст більш “вузьким”, винести різні дані в різні контексти. Або використовувати бібліотеки на кшталт valtio, jotai чи Redux.
Приклад “вузького” контексту:
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
Тепер зміна теми не впливає на інші дані, бо вони знаходяться в окремих контекстах.
Не оновлювати стан без потреби
Якщо в певний момент код викликає setState, який не змінює реальних значень, все одно відбудеться рендер. Перевірте, чи дійсно потрібно відправляти setState.
Використання “key” при списках
Іноді при відтворенні списків чи таблиць некоректна прив’язка key призводить до зайвого рендеру елементів.
Демонстрація
Нехай у нас є Parent, що підключає Child із пропом handleClick:
function Parent() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
return (
<div>
<Child onClick={increment} />
<p>Count: {count}</p>
</div>
);
}
const Child = React.memo(function Child({ onClick }) {
console.log('Child render');
return <button onClick={onClick}>Increment</button>;
});
Проблема: навіть з React.memo, Child буде рендеритись при кожному оновленні count, оскільки increment
створюється наново (нове посилання). Щоб це виправити, використовують useCallback:
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount(c => c + 1), []);
return (
<div>
<Child onClick={increment} />
<p>Count: {count}</p>
</div>
);
}
Тепер Child не буде перерисовуватись при зміні count, доки проп onClick лишається тим самим посиланням (і не змінюється, бо useCallback має порожній масив залежностей).
Загальний алгоритм
- Виявити зайві рендери.
- Перевірити, чи компонент має React.memo (якщо це функціональний компонент).
- Забезпечити незмінність пропів та стану там, де це можливо.
- Переглянути підходи до контексту (або використовувати бібліотеки з більш ефективним оновленням).
- Видалити зайві setState виклики.
- Мемоізувати анонімні функції, об’єкти.
Висновок
React успішно оптимізує рендеринг, проте зайві рендери можуть “з’їдати” продуктивність, особливо у складних додатках. Застосування таких інструментів, як React.memo, useCallback, useMemo, а також розумне використання контексту і дотримання чистоти стана допомагає уникнути багатьох непотрібних повторних відтворень. Аналіз своїх компонентів за допомогою DevTools і профайлінгу допоможе знайти ті місця, де варто застосувати оптимізацію, і зробити ваш React-код швидшим і ефективнішим.
Від jsPDF до Chrome: вирішення складної задачі рендерингу PDF з таблицями

Створення PDF-документів із таблицями в JavaScript може стати складним викликом, особливо коли потрібно дотримуватися гарного дизайну та враховувати різні розміри сторінок, форматування, шрифти тощо. Є декілька інструментів і бібліотек, що дають змогу генерації PDF у браузері або на сервері, серед найпопулярніших — jsPDF, а також підхід через рендеринг HTML у Chrome (headless). У цій статті розглянемо, які нюанси варто врахувати під час складного рендерингу, як створювати таблиці за допомогою jsPDF і коли краще використовувати інші підходи, такі як headless Chrome.
Чому складно генерувати PDF із таблицями
Проблеми з flow-логікою
Більшість бібліотек JavaScript не мають автоматичного “перенесення” рядків таблиці на наступну сторінку з урахуванням заголовків і футерів.
Обмежений набір інструментів для дизайну
jsPDF пропонує API для малювання ліній, тексту тощо, але щоб зробити адаптивну таблицю або “вписати” її в сторінку, потрібно писати додаткову логіку.
Шрифти та локалізація
Якщо потрібно використовувати шрифти з Unicode (кирилиця, ієрогліфи), доведеться підключати додаткові файли шрифтів і налаштовувати бібліотеку.
Довгі списки даних
Якщо обсяг даних великий (декілька сотень чи тисяч рядків), формування PDF може бути занадто повільним у браузері або вимагати нетривіальних оптимізацій.
Підходи до рендерингу PDF із таблицями
- jsPDF і додаткові плагіни
jsPDF — досить популярна бібліотека, яка дає змогу “з нуля” малювати на PDF-канві. Для таблиць існує плагін jspdf-autotable, що спрощує побудову таблиці, відстеження ширини та висоти колонок, перенесення рядків тощо.
Приклад використання jsPDF з плагіном autotable
import jsPDF from 'jspdf';
import 'jspdf-autotable';
function generatePdf(data) {
const doc = new jsPDF('p', 'pt', 'a4');
const columns = [
{ header: 'ID', dataKey: 'id' },
{ header: 'Name', dataKey: 'name' },
{ header: 'Email', dataKey: 'email' },
];
doc.autoTable({
head: [columns.map(col => col.header)],
body: data.map(row => columns.map(col => row[col.dataKey])),
startY: 50,
margin: { left: 40, right: 40 },
theme: 'grid',
didDrawPage: (dataArg) => {
doc.text('Report', 40, 30);
},
});
doc.save('table.pdf');
}
Переваги та недоліки
- Перевага: Можна працювати прямо в браузері, швидко створювати прості таблиці.
- Недолік: Якщо таблиця дуже складна (багаторівневі заголовки, колSPAN, rowSPAN), треба писати кастомний код.
- Використання headless Chrome
Підхід через headless Chrome (наприклад, за допомогою Puppeteer) дозволяє рендерити HTML-сторінку до PDF. Це означає, що:
- Можемо використати звичайну верстку (HTML+CSS) для побудови таблиці.
- Chrome сам визначить розміри сторінок, зробить перенесення на наступні сторінки, відобразить усі стилі.
Приклад використання Puppeteer для генерації PDF:
const puppeteer = require('puppeteer');
async function generatePdfUsingChrome(htmlContent) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
await page.pdf({
path: 'output.pdf',
format: 'A4',
printBackground: true,
});
await browser.close();
}
Що робимо:
- Формуємо HTML-шаблон із таблицями (можна використати CSS для стилювання).
- Передаємо цей HTML у page.setContent().
- Викликаємо page.pdf(), що створює готовий PDF-файл.
Коли обирати headless Chrome
- Потрібна складна верстка з багатьма стилями, адаптивна таблиця, кілька сторінок, колSPAN/rowSPAN, заголовки при перенесенні сторінки.
- Маємо серверну частину (наприклад, Node.js), що може запускати Puppeteer.
- Хочемо full-результат, максимально схожий на друковану версію веб-сторінки.
Поради щодо продуктивності та якості
Оптимізація для jsPDF
- Менше операцій
Якщо таблиця дуже довга, генерування кожного рядка у режимі реального часу може бути повільним. Варто підготувати дані та викликати autoTable один раз.
- Правильні шрифти
Якщо потрібна кирилиця чи інші символи, слід підключити шрифт: doc.addFileToVFS(‘MyFont.ttf’, fontData), doc.addFont(‘MyFont.ttf’, ‘MyFont’, ‘normal’). Інакше символи можуть відображатися некоректно.
Для headless Chrome
- Використовуйте CSS-правила для друку
Наприклад: @page { size: A4; margin: 1cm; } table { page-break-inside: auto; } tr { page-break-inside: avoid; }
- Налаштуйте page.pdf
path, format, printBackground (true або false), щоб отримати потрібний результат.
- Можливе використання cache, якщо потрібно часто генерувати один і той самий звіт (змінювати тільки дані).
Обробка дуже великих таблиць
Якщо таблиця має тисячі рядків, обидва методи можуть уповільнитися. Можливі варіанти:
- Розбивати таблицю на кілька сегментів (пагінація).
- Якщо користувачу потрібен повний звіт, генерувати офлайн на сервері, а потім віддавати посилання на результат.
Якщо таблиця настільки велика, що PDF стає десятками мегабайт, можливо слід запропонувати інший формат (Excel).
Приклади змішаних підходів
- Використання jsPDF для формування титульної сторінки, а потім рендеринг довгої таблиці через headless Chrome, і об’єднання двох PDF (через PDF-кидану бібліотеку).
- Генерування HTML у браузері і передача його на сервер, де запускається Puppeteer, повертаючи PDF.
Висновок
Створення PDF із таблицями може бути складним завданням, особливо коли потрібна підтримка багатьох стилів, перенесення сторінок, локалізовані шрифти. jsPDF дозволяє швидко генерувати документи безпосередньо в браузері, але вимагає додаткових плагінів та логіки для складних таблиць. Якщо верстка дуже складна, може стати у пригоді підхід через headless Chrome, де верстка робиться звичайним HTML+CSS і Puppeteer генерує кінцевий PDF.
Обидва методи мають свої переваги й недоліки, вибір залежить від конкретних вимог до дизайну, обсягу даних, середовища виконання і потреби в інтерактивності. Важливо врахувати продуктивність (особливо для великих таблиць) та правильну обробку шрифтів і локалізації. Тоді і jsPDF, і Chrome headless стануть потужними інструментами для вирішення складного рендерингу PDF.
Динамічні форми з Flask: створення та обробка змінних полів

Flask є одним із найпопулярніших мікрофреймворків для розробки веб-додатків на Python. Завдяки його простоті та гнучкості розробники можуть швидко створювати додатки, де частини логіки реалізовано у різних бібліотеках (наприклад, для роботи з формами). Одна з цікавих задач — реалізація динамічних форм, які можуть змінювати кількість полів або типи полів залежно від обставин. У цій статті ми розглянемо, як створити та обробляти такі “живі” форми з Flask, звертаючи увагу на використання Flask-WTF, генерацію форм на льоту і роботу з шаблонами Jinja2.
Що таке динамічні форми
Динамічна форма — це форма, яка будується або доповнюється у реальному часі, залежно від даних, введених користувачем, інформації з бази даних чи інших умов. Наприклад:
- Залежно від того, скільки товарів у кошику, ми виводимо різну кількість полів для їх редагування.
- Залежно від вибраного типу об’єкта (наприклад, “Книга” чи “Фільм”) змінюємо набір полів (автор, режисер, тривалість).
- Дозволяємо користувачам додавати нові поля у формі (динамічно додаючи блоки “Додати ще один номер телефону” тощо).
Основний інструментарій
Flask Головний фреймворк для створення маршрутизації та логіки. Flask-WTF Бібліотека, що спрощує роботу з формами, використовує WTForms. Надає зручний спосіб описувати поля і валідацію на Python. Jinja2 Шаблонізатор, що дозволяє динамічно рендерити HTML, включно з формами.
Приклад простої форми у Flask-WTF
Почнемо з найпростішого варіанта. Створимо просту форму, яка містить декілька полів.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class SimpleForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
app.py
from flask import Flask, render_template, redirect, url_for
from forms import SimpleForm
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecret'
@app.route('/', methods=['GET', 'POST'])
def index():
form = SimpleForm()
if form.validate_on_submit():
name = form.name.data
return redirect(url_for('success', username=name))
return render_template('index.html', form=form)
@app.route('/success/<username>')
def success(username):
return f'Hello, {username}!'
index.html (шаблон)
<!DOCTYPE html>
<html>
<head>
<title>Simple Form</title>
</head>
<body>
<form method="POST" action="">
{{ form.csrf_token }}
{{ form.name.label }}: {{ form.name(size=20) }} <br>
{{ form.submit() }}
</form>
</body>
</html>
Це статична форма (кількість полів і тип полів визначені наперед). Далі розглянемо випадок, коли поля динамічні.
Динамічна зміна кількості полів
Одне з найтиповіших завдань — коли кількість полів невідома заздалегідь. Припустимо, у нас є список телефонів, що треба відобразити у формі, і користувач може додавати нові або видаляти наявні.
Підхід 1: Генерація форм у шаблоні
Можна не описувати поля у WTForms, а динамічно створювати поля у шаблоні (наприклад, цикл for для phoneNumbers). Проте тоді ми втрачаємо частину автоматичної валідації.
Підхід 2: FieldList i FormField (WTForms)
WTForms має FieldList, що дає змогу динамічно працювати зі списком підформ.
forms.py
from flask_wtf import FlaskForm
from wtforms import Form, StringField, FieldList, FormField, SubmitField
class PhoneForm(Form):
phone_number = StringField('Phone Number')
class DynamicForm(FlaskForm):
phones = FieldList(FormField(PhoneForm), min_entries=1, max_entries=5)
submit = SubmitField('Submit')
app.py
@app.route('/dynamic', methods=['GET', 'POST'])
def dynamic():
form = DynamicForm()
if form.validate_on_submit():
phone_data = [p.phone_number.data for p in form.phones]
print('Phones:', phone_data)
return 'Data saved!'
return render_template('dynamic.html', form=form)
dynamic.html
<!DOCTYPE html>
<html>
<head>
<title>Dynamic Form</title>
</head>
<body>
<form method="POST">
{{ form.csrf_token }}
<ul>
{% for subform in form.phones %}
<li>{{ subform.phone_number.label }} {{ subform.phone_number() }}</li>
{% endfor %}
</ul>
{{ form.submit() }}
</form>
</body>
</html>
У цьому прикладі поле phones (FieldList) містить від 1 до 5 телефонів. Коли користувач надсилає форму, Flask-WTF зчитує дані і відображає їх у form.phones.
Проте, якщо треба динамічно додавати або видаляти поля без перезавантаження сторінки, потрібно застосовувати JavaScript, щоб “клонувати” HTML-елемент і додавати його у form. При відправленні ці поля приймають імена у вигляді phones-0-phone_number, phones-1-phone_number і так далі.
Умовна логіка для різних полів
Другий сценарій: у нас є один і той самий route, проте якщо користувач обирає опцію “Фізична особа”, потрібні поля ім’я та прізвище, а якщо “Компанія”, тоді поля назви компанії і код ЄДРПОУ. Можна згенерувати поля залежно від обраного типу.
Підхід: створити кілька форм-класів або одну велику з усіма полями, а в шаблоні вже вирішувати, що показувати. Але обробка стане складнішою.
Реактивна зміна з боку клієнта
Для більш інтерактивних змін (наприклад, якщо користувач обрав “Компанія”, то підтягуємо базу даних і показуємо список можливих напрямів), часто використовують JS на клієнтському боці, який викликає AJAX запит до Flask, потім Flask повертає JSON з переліком потрібних полів, і JS динамічно малює форму.
Ключові моменти для покращення досвіду
Безпека
Якщо форма має динамічно змінювану структуру, перевіряйте (наприклад, через CSRF-токен), що ця форма справді походить з вашого сайту, а не з підробленого.
Валідація
Залежно від логіки, може знадобитися різна валідація (наприклад, якщо поле номер телефону — валідація формату, якщо поле email — перевірка поштового формату).
Зберігання даних
Якщо форми стають дуже розгалуженими, варто продумати структуру бази даних (один-до-багатьох для телефонів, таблиці для різних типів сутностей).
Підтримка
Іноді варто створювати свою функцію для побудови форми, що приймає параметри або конфігурацію з бази, а повертає готову форму WTForms.
Висновок
Динамічні форми у Flask можуть бути реалізовані кількома методами: від простого генераторного підходу у шаблонах (через for-цикли) до використання FieldList та FormField у Flask-WTF. Такі інструменти як JavaScript (для наочного додавання/видалення полів на льоту) і WTForms (для валідації) доповнюють один одного. Важливо продумати, яка саме логіка у вас: чи потрібен просто список полів (FieldList), чи умовний набір полів залежно від типу користувача, чи взагалі складний сценарій, де форма переробляється за запитом із сервера. Водночас безпека та коректна валідація мають бути на висоті, аби динамічна форма була зручною і не створювала шпарин у коді.
Просунуте використання бібліотеки React Router: як спростити складну навігацію та покращити продуктивність

У складних React-застосунках, де існує багато маршрутів і динамічної логіки, керування навігацією може стати викликом. React Router v7 надає набір нових та покращених можливостей, що дають змогу ефективніше будувати маршрути, спростити роботу з компонуванням сторінок і навіть підвищити продуктивність. У цій статті розглянемо ключові особливості React Router v7 (у порівнянні з попередніми версіями), обговоримо, як ними користуватися, та звернемо увагу на поради щодо покращення як швидкодії, так і зручності розробки.
Основні нові можливості React Router v7
- Об’єктний синтаксис опису маршрутів
- Покращена робота з асинхронним завантаженням даних
- Додаткові інструменти для підвищення продуктивності
- Можливості для складнішої навігації з умовними та вкладеними маршрутами
Об’єктний синтаксис опису маршрутів
У React Router історично застосовували JSX-елементи <Route> та <Routes> для визначення, який компонент відображати при певному шляху. У версії 7 користувачі можуть використовувати новий об’єктний (JavaScript) підхід:
Приклад (псевдокод):
import { createBrowserRouter } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
path: '',
element: <HomePage />,
},
{
path: 'users',
element: <UsersPage />,
children: [
{
path: ':id',
element: <UserProfile />,
},
],
},
{
path: 'settings',
element: <SettingsPage />,
},
],
},
]);
export default router;
Замість звичного <Routes> та <Route> можна створювати масив об’єктів, які описують шляхи (path), компоненти (element) і вкладені маршрути (children). Цей підхід дає змогу зберігати декларацію маршрутів в одному місці. Така централізована конфігурація полегшує:
- глобальний рефакторинг маршрутів;
- створення додаткових інструментів (наприклад, автоматизація генерації меню);
- розділення конфігурації за модульною структурою.
Покращена робота з асинхронним завантаженням даних
Якщо ваш застосунок має складну логіку завантаження даних для різних сторінок, React Router v7 пропонує функції, що допомагають керувати цією асинхронністю. Існують підходи (loader, action, інші), коли логіку завантаження даних можна визначити безпосередньо у маршрутах.
Це спрощує:
- попереднє завантаження даних для сторінки (до рендеру);
- обробку помилок і редіректів, якщо дані не знайдено.
Псевдокод (можливий формат):
{
path: 'users/:id',
element: <UserProfile />,
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.id}`);
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
},
}
Після цього у компоненті UserProfile можна використати хук useLoaderData(), щоб отримати дані.
Інструменти для підвищення продуктивності
- Код-сплітинг (code splitting)
React Router сумісний з React.lazy та Suspense. Це дає змогу завантажувати сторінку лише коли користувач переходить на відповідний маршрут. Таким чином зменшується початковий розмір бандлу і прискорюється завантаження.
const SettingsPage = React.lazy(() => import('./pages/SettingsPage'));
{
path: 'settings',
element: (
<React.Suspense fallback={<Loading />}>
<SettingsPage />
</React.Suspense>
)
}
- Вкладені (nested) маршрути
Замість повного повторного рендеру під час переміщення між вкладеними шляхами, React Router здійснює рендер лише потрібної частини дерева компонентів. Це пришвидшує роботу.
Складні сценарії з умовною навігацією
- Вкладені маршрути з guard-логікою (захист)
Якщо потрібно дозволяти доступ лише авторизованим користувачам, можна додати перевірку в loader чи action і виконувати redirect.
{
path: 'dashboard',
element: <DashboardLayout />,
loader: async () => {
const user = await checkUserSession();
if (!user) {
throw redirect('/login');
}
return user;
},
children: [...]
}
- Переадресація (redirect)
Якщо треба тимчасово перенаправляти користувача зі старого маршруту на новий:
{
path: 'old-route',
loader: () => {
throw redirect('/new-route');
}
}
- Обробка помилок (errorElement)
Якщо loader або action генерують помилку, у React Router v7 можна вказати спеціальний компонент для відображення помилки.
{
path: 'users',
errorElement: <ErrorFallback />,
children: [...]
}
Кращі практики для покращення зручності розробки
- Поділ маршрутового дерева на модулі
Якщо застосунок великий, розділіть routes на окремі файли (наприклад, userRoutes, adminRoutes). Потім об’єднайте їх у головній конфігурації.
- Використання TypeScript
Оскільки React Router v7 має кращу інтеграцію з TypeScript, варто описувати типи параметрів маршрутів, щоб уникати помилок.
- Власні хуки
Створюйте власні хуки на основі useLoaderData, useActionData, щоб приховувати деталі завантаження даних і покращувати читабельність коду.
Особливості оптимізації
- Вибір стратегії Suspense
Вирішуйте, як саме відображати стан завантаження (шаблон скелету, спінер). Користувацький досвід покращується, якщо блоки інтерфейсу завантажуються поступово.
- Використання memo і useMemo
Якщо у певних компонентах багато обчислень, застосовуйте useMemo, React.memo (для дочірніх компонентів). Це зменшить кількість повторних рендерів під час зміни маршруту.
- Анонімні функції та стрілкові функції у пропсах
Спробуйте уникати стрілкових функцій у JSX, якщо вони часто викликають повторні рендери. Краще виносити колбеки у useCallback, якщо це критично для продуктивності.
Висновок
React Router v7 робить роботу з маршрутизацією в React-додатках більш гнучкою та зручною. Новий об’єктний формат опису маршрутів, інтеграція з асинхронними функціями (loader, action) та можливість спрощеної організації вкладених маршрутів надають розробникам інструменти для створення складної навігації без великого обсягу допоміжного коду. Крім того, покращення у продуктивності (підтримка code splitting, ефективне рендерування лише потрібних частин сторінки) дають змогу масштабувати додаток, не жертвуючи швидкодією. Щоб використати всі переваги React Router v7, рекомендовано спланувати структуру маршрутів, застосовувати асинхронні можливості (loader, errorElement) і пам’ятати про оптимізацію (код-сплітинг, memo). Це допоможе не лише спростити складну навігацію, а й зробити ваш додаток більш стійким до майбутніх змін і оновлень.
Рефакторинг за допомогою codemods для автоматизації змін API

Уявіть, що вам потрібно здійснити масштабну зміну API у великому проєкті: перейменувати методи, змінити назви параметрів чи навіть перевпорядкувати аргументи у сотнях файлів. Зробити все вручну вкрай важко і ризиковано. Тут на допомогу приходять codemods — спеціальні скрипти, які можуть автоматизувати рефакторинг, використовуючи абстрактне синтаксичне дерево (AST). У цій статті поговоримо, навіщо потрібні codemods, як вони працюють і як вони допомагають у рефакторингу API.
Що таке codemods
Codemod — це скрипт, який аналізує вихідний код на рівні синтаксичного дерева, а потім перетворює його за певними правилами. В англомовній спільноті це часто називають AST transform, бо основна робота виконується через розбір коду в AST, внесення змін і генерацію коду назад.
Основні цілі codemods:
- Масштабний рефакторинг: заміна застарілого API, зміна назв змінних чи функцій.
- Міграція з однієї бібліотеки на іншу: автоматичне переписування імпортів і викликів.
- Усунення технічного боргу: оновлення коду відповідно до нових правил ESLint або стилю.
Навіщо потрібні codemods при зміні API
Коли велика бібліотека чи фреймворк випускає нову версію з оновленим API, розробникам часто потрібно:
- Змінити сигнатури функцій
- Перейменувати об’єкти чи методи
- Переписати імпорти (наприклад,
@angular/common/http
замість @angular/http
)
- Видалити застарілий (deprecated) код
Зробити це вручну у великому кодовому базисі майже нереально. Codemods дають змогу виконати зміни автоматично за допомогою одного скрипта.
Приклад інструментів
jscodeshift
jscodeshift — це популярна утиліта від Facebook (Meta), яка використовує Babel або recast для роботи з кодом. Вона написана на Node.js і може швидко обходити всі файли у вашому проєкті.
Встановлення:
npm install -g jscodeshift
Приклад запуску:
jscodeshift -t renameFunction.js src/
Де renameFunction.js
— це ваш скрипт-трансформатор.
Babel transformations
Babel теж дає можливість писати власні трансформери (плагіни), але jscodeshift вважають більш спеціалізованим рішенням для codemods.
TS-morph або Typescript Compiler API
Якщо вам треба більш типобезпечний підхід або якщо код на TypeScript, можна використати ts-morph чи офіційний TypeScript Compiler API.
Приклад: перейменування методу в API
Припустімо, що у вас є функція getUser()
в десятках файлів, яку потрібно перейменувати на fetchUser()
. За допомогою jscodeshift це можна робити так:
- Створити файл
renameFunction.js
:
module.exports = function(file, api) {
const j = api.jscodeshift
const root = j(file.source)
root.find(j.CallExpression, {
callee: {
type: 'Identifier',
name: 'getUser'
}
})
.forEach(path => {
path.value.callee.name = 'fetchUser'
})
return root.toSource()
}
- Запустити:
jscodeshift -t renameFunction.js src/
Скрипт пройдеться по всіх файлах у папці src/
і замінить виклики getUser(...)
на fetchUser(...)
.
Приклад: міграція іменованих імпортів
Уявімо, що раніше ми імпортували метод parseData
з import { parseData } from './utils'
, а тепер він називається processData
.
- Скрипт
migrateImports.js
:
module.exports = function(file, api) {
const j = api.jscodeshift
const root = j(file.source)
root.find(j.ImportDeclaration, { source: { value: './utils' } })
.forEach(path => {
path.value.specifiers.forEach(specifier => {
if (specifier.imported.name === 'parseData') {
specifier.imported.name = 'processData'
}
})
})
return root.toSource()
}
- Запуск:
jscodeshift -t migrateImports.js src/
Поради для успішного використання codemods
- Створіть резервну копію коду або використовуйте систему контролю версій, щоб завжди можна було відкотити зміни.
- Пиши маленькі, інкрементальні скрипти: краще кілька маленьких трансформерів, ніж один великий, що робить все.
- Тестуйте скрипт на невеликій ділянці коду перед тим, як застосовувати до всього проєкту.
- Використовуйте типи (для TypeScript) якщо це можливо, щоб більш точно знаходити потрібні місця.
- Підключайте linter і форматер (наприклад, ESLint і Prettier) до фінального кроку, щоб отриманий код був читабельним.
Коли codemods не допоможуть
- Якщо логіка зміни складна і залежить від конкретного контексту, який важко відстежити в коді. Наприклад, якщо функцію викликають з різними аргументами залежно від умов, і ваша трансформація залежить від того, якими саме аргументами.
- Якщо API має дуже різні сценарії використання, які не вкладаються у шаблони.
Попри це, більшість механічних змін, як-от перейменування, зміна сигнатур функцій, видалення застарілих методів, можуть бути автоматизовані.
Висновок
Рефакторинг великого проєкту, в якому потрібно внести глобальні зміни в API, може забрати багато часу, якщо робити його вручну. Codemods дають змогу автоматизувати більшу частину роботи, зменшують ризик людських помилок і роблять процес масштабного оновлення коду більш прозорим.
Завдяки jscodeshift або аналогічним інструментам розробники можуть швидко оновлювати імпорти, перейменовувати функції та навіть змінювати структуру коду за кілька хвилин, а не днів. Головне — проєкт має бути підконтрольний системі версій, а самі скрипти краще писати й тестувати покроково.
Якщо у вашій команді часто змінюється API або ви готуєтеся до великого оновлення фреймворку чи бібліотек, варто розглянути можливості codemods як частини процесу рефакторингу. Це інвестиція, що швидко окупиться у вигляді зекономленого часу і покращення якості коду.
Як тече пам’ять, якщо ви забудете скасувати підписку Observable

У сучасних веб-додатках, що використовують RxJS (наприклад, у Angular), Observables широко застосовуються для асинхронної роботи. Якщо підписка на Observable не скасована, це може призвести до витоку пам’яті. У цій статті розглянемо, як саме відбувається витік, чому забута відписка викликає проблеми і як цьому запобігти.
Витік пам’яті
Витік пам’яті означає, що об’єкти, які більше не потрібні, залишаються в пам’яті та не знищуються збирачем сміття. У контексті Observables це відбувається, коли додаток продовжує зберігати посилання на підписувача, навіть після того, як компонент (чи інший об’єкт) вже знищений. Збирач сміття бачить, що цей об’єкт, який мав би бути видалений, все ще використовується, тому не звільняє його пам’ять.
Як працює Observable та підписка
Observable є джерелом асинхронних даних, яке надсилає значення або події своїм підписувачам. Поки триває підписка, Observable може надсилати нові дані в будь-який момент. Щоб зупинити надсилання даних, звичайно виконують одну з дій:
- complete()
- error()
- unsubscribe()
Якщо Observable не завершується автоматично (наприклад, без кінця надсилає події), а розробник не викликає unsubscribe(), підписка лишається активною.
Чому забута відписка викликає проблеми
Якщо компонент підписався на Observable і забув відписатися, Observable далі триматиме посилання на цей компонент. Навіть коли компонент знищується (наприклад, користувач пішов на іншу сторінку), об’єкт компонента не може бути прибраний із пам’яті, оскільки існує активна підписка. Це призводить до накопичення непотрібних об’єктів і зростання використання пам’яті. З часом такий витік може викликати зниження продуктивності та інші збої.
Приклад в Angular
Нижче наведено простий приклад. Якщо компонент не викликає unsubscribe(), він ризикує залишити підписку активною і спровокувати витік пам’яті.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { SomeService } from './some.service';
@Component({
selector: 'app-example',
template: `...`
})
export class ExampleComponent implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private someService: SomeService) {}
ngOnInit() {
this.subscription = this.someService.getData().subscribe(data => {
console.log('Отримано дані:', data);
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Коли забути відписатися
Уявімо, що у додатку є список компонентів ExampleComponent, який з’являється і зникає. Якщо кожен екземпляр створює нову підписку і не відписується, кожен об’єкт далі лишатиметься в пам’яті. Через деякий час це може викликати зависання програми або зростання споживання ресурсів.
Наслідки
- Збільшення обсягу пам’яті. Додаток почне споживати дедалі більше ресурсів.
- Погіршення продуктивності. Багато непотрібних об’єктів у пам’яті негативно впливають на швидкість.
- Непередбачувана поведінка. Логіка може продовжувати виконуватися для об’єктів, яких фактично вже не існує в інтерфейсі.
Як уникнути витоків
Виклик unsubscribe()
Найпростіше рішення — запам’ятати Subscription і викликати unsubscribe() у методі життєвого циклу (наприклад, ngOnDestroy у Angular).
Використання takeUntil()
У RxJS можна застосувати підхід із Subject. Коли компонент знищується, Subject надсилає сигнал, і всі підписки з оператором takeUntil(this.destroy$) автоматично відписуються.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { SomeService } from './some.service';
@Component({
selector: 'app-example',
template: `...`
})
export class ExampleComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
constructor(private someService: SomeService) {}
ngOnInit() {
this.someService.getData()
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
console.log('Отримано дані:', data);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
При знищенні компонента destroy$.next() завершує всі підписки, що використовують takeUntil(this.destroy$).
Чистка в масиві
Якщо у компоненті є кілька підписок, їх можна зберігати у масив. У ngOnDestroy пройтися цим масивом і викликати unsubscribe() для кожної підписки.
Ознаки витоку
Якщо ви помічаєте:
- Зростання використання пам’яті з часом
- Логіка компонента викликається, хоча компонент уже не в інтерфейсі
- Повільну роботу або зависання
Швидше за все, десь забули відписатися від Observable.
Висновок
Витік пам’яті через невідписані підписки — одна з найпоширеніших проблем в Angular та інших середовищах, де застосовуються Observables. Щоб цього уникнути, достатньо:
- Використовувати unsubscribe() у життєвому циклі компонента
- Або організувати takeUntil() із Subject
- Правильно використовувати async пайп чи інші підходи для автоматичної відписки
Ці способи допомагають зберегти стабільність та продуктивність вашого застосунку, запобігаючи накопиченню непотрібних об’єктів у пам’яті.
Тестування розширень VS Code за допомогою WebdriverIO (ключові нюанси)

Visual Studio Code (VS Code) є одним з найпопулярніших редакторів коду. Його екосистема розширень дає розробникам можливість додавати нові функції та кастомізувати робоче середовище. Проте автоматизоване тестування цих розширень може бути складним завданням. Саме тут стає у нагоді WebdriverIO. У цій статті розглядається, як за допомогою WebdriverIO автоматизувати тести для розширень VS Code, звертаючи увагу на особливості цього процесу.
Навіщо потрібне автоматизоване тестування розширень VS Code
Підтримка якості Розширення можуть бути досить складними, особливо якщо вони працюють з різними мовами програмування чи інтегруються із зовнішніми інструментами. Автоматизовані тести допомагають переконатися, що нові версії не ламають уже існуючий функціонал.
Виявлення регресій Автоматизовані тести дають змогу швидше виявляти регресії. Якщо зміни в коді призводять до помилок, тести вкажуть на них, даючи час виправити їх до виходу оновлення.
Швидше впровадження Ручне тестування може забирати багато часу. Завдяки автоматизованим тестам можна швидко ітеративно розвивати функціонал, зосереджуючись на створенні нових можливостей замість постійного повторення одних і тих самих перевірок
Роль WebdriverIO
WebdriverIO зазвичай використовують для автоматизації веб-тестів, оскільки це фреймворк на базі JavaScript, що працює з Node.js. Проте з додатковими драйверами чи службами його можна застосовувати і для інших типів додатків.
Чому WebdriverIO
- Середовище JavaScript. WebdriverIO природно вписується у робочий процес з Node.js, знайомий розробникам під розширення VS Code.
- Архітектура з плагінами. WebdriverIO має багато сервісів та плагінів, що дають змогу пристосувати тести до різних сценаріїв.
- Підтримка async/await. Це робить тестовий код чистішим і легшим для розуміння.
Складнощі під час тестування розширень VS Code
Замість браузера – Electron Звичайні веб-тести працюють у браузері з headless-режимом. VS Code є десктоп-додатком на Electron, отже керувати ним так само просто, як веб-сторінкою, не вийде.
Пошук елементів інтерфейсу У Electron-додатку структура і селектори можуть відрізнятися від стандартної DOM-структури. Можливо, доведеться використовувати спеціальні атрибути чи нестандартні засоби пошуку.
Активація розширення Розширення VS Code потрібно активувати. Зазвичай воно чекає на певні події, команди чи інші тригери, перш ніж стане доступним для взаємодії.
Підходи до автоматизації VS Code за допомогою WebdriverIO
Виділимо принаймні два варіанти тестування функціоналу VS Code:
Використання code-server або веб-орієнтованих збірок VS Code Деякі проєкти, наприклад code-server, дають змогу запускати VS Code у веб-середовищі. Це спрощує автоматизацію через WebdriverIO, оскільки веб-інтерфейс нагадує звичайний сайт. Кроки:
- Встановити code-server (чи іншу веб-збірку).
- Запустити, отримавши адресу (наприклад http://localhost:8080).
- WebdriverIO підключається до цієї адреси, після чого працюємо із CSS-селекторами і ін.
Переваги: Звичні інструменти для тестування веб-сторінок. Недоліки: Може відрізнятися від офіційного VS Code.
Використання Spectron чи Playwright-підходів Існувала бібліотека Spectron для тестування Electron, але також можливо поєднати WebdriverIO з кастомними драйверами, щоб керувати Electron-додатком. Кроки:
- Запустити VS Code як Electron-додаток.
- Підключитися через спеціальний драйвер.
- Керувати елементами, відправляти команди і т.д.
Перевага: Тестується реальне середовище VS Code. Недолік: Складна конфігурація і менше готової документації.
Приклад налаштування WebdriverIO (веб-підхід)
Встановлення
npm install webdriverio @wdio/cli --save-dev
Налаштування wdio.conf.js
Після запуску wdio config wizard отримаємо файл конфігурації, де вказуємо:
- local mode
- тестовий фреймворк (Mocha чи Jest)
- baseUrl
Зразок тесту
describe("VS Code Extension Test", () => {
it("should install and activate the extension", async () => {
// Припустимо, що code-server чи веб-варіант VS Code працює
await browser.url("https://localhost:8080");
// Приклад: знаходимо іконку менеджера розширень
const extensionIcon = await $("[data-test='extensions-icon']");
await extensionIcon.click();
// Далі: пошук розширення
const searchBox = await $("[data-test='extensions-search']");
await searchBox.setValue("my-extension");
// Дочекатися і перевірити результат
// Можливо встановлення і подальша перевірка
});
});
Типові проблеми
Затримки (timeouts) VS Code може повільно стартувати. Варто встановлювати достатній timeout або використовувати методи на кшталт waitUntil.
Селектори Electron UI може не мати стандартних HTML-ідентифікаторів. Можливо, потрібно додати атрибути data-test.
Права та політики Якщо розширення вимагає доступу до файлової системи, можуть з’являтися діалогові вікна. Потрібна логіка для обробки.
Рекомендації для стабільного набору тестів
Окреме середовище Найкраще мати чисте оточення (Docker або віртуальний образ), щоб тести щоразу стартували з нуля.
Очищення даних Якщо ваше розширення зберігає будь-які налаштування чи кеш, чистіть їх після тесту.
Послідовність чи паралель Якщо тестові сценарії конфліктують, запускайте їх по черзі. Якщо незалежні, можна паралельно.
Висновок
Автоматизація тестування розширень для VS Code відрізняється від звичайного веб-тестування, адже потрібно взаємодіяти з Electron-додатком або з веб-версією (code-server). WebdriverIO залишається корисним інструментом, якщо правильно налаштувати середовище і локатори. Зрештою, створення надійної системи тестів забезпечить стабільність розширення і покращить досвід користувачів. При належному підході до тайм-аутів, збирання даних та очищення довкілля, WebdriverIO може успішно тримати високу планку якості для VS Code Extensions.
Одне React-завдання, яке демонструє ключові навички на співбесіді

Співбесіди на позиції React-розробника часто включають тестове завдання чи live-coding, де потрібно за короткий час продемонструвати свої знання. Одне з найкращих способів оцінити кандидата – дати йому цілісне завдання, яке покриє кілька ключових аспектів React: роботу зі станом, взаємодію з API, обробку подій та оптимізацію. У цій статті ми розглянемо одне завдання, яке можна попросити виконати на співбесіді, і розберемо, чому воно показує ключові навички.
Опис завдання
- Завдання: Створити простий TODO-додаток із можливістю:
- Додавати нові завдання;
- Відмічати їх як виконані;
- Видаляти їх із списку;
- Зберігати та отримувати список завдань із фейкового API (можна використати mock API або json-server).
- Вимоги:
- Використати React Hooks (useState, useEffect) або Redux (залежно від вимог співбесіди);
- Зробити форму додавання завдання;
- Продемонструвати обробку помилок (наприклад, якщо API недоступний).
- Оптимізація:
- Реалізувати мінімальний кеш або показати, як уникнути зайвих запитів;
- Пояснити підхід до мемоізації (наприклад, useMemo або React.memo).
- UI (базовий або із бібліотекою Material UI / Bootstrap) – не критично, головне показати інтуїтивну взаємодію.
Чому саме TODO-додаток?
- Розуміння стану (state management): TODO-список відмінно демонструє роботу зі станом: додавання, видалення й оновлення елементів.
- Обробка користувацьких подій: Додавання завдання пов’язане з формою, а відмітка “cвиконано” – з чекбоксом.
- Взаємодія з API: Можливість завантажувати завдання з сервера та відправляти зміни.
- Оптимізація: Якщо додаток стає більшим, потрібно думати про ефективні ререндери.
Приклад рішення
1. Структура проєкту
src/
components/
TodoList.js
TodoItem.js
AddTodoForm.js
App.js
api.js
Пояснення:
TodoList.js
: відображає список завдань.
TodoItem.js
: окремий компонент для кожного пункту.
AddTodoForm.js
: компонент із формою додавання нового завдання.
api.js
: модуль для взаємодії з API (запити та обробка помилок).
App.js
: головний компонент, координує всі дії.
2. API (mock-варіант)
api.js – імітуємо роботу з сервером:
const todos = [
{ id: 1, text: 'Вивчити React', completed: false },
{ id: 2, text: 'Прочитати статтю про Redux', completed: true },
];
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
export async function getTodos() {
await delay(500);
return todos;
}
export async function addTodo(text) {
await delay(300);
const newTodo = { id: Date.now(), text, completed: false };
todos.push(newTodo);
return newTodo;
}
export async function toggleTodo(id) {
await delay(200);
const todo = todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
return todo;
}
export async function deleteTodo(id) {
await delay(200);
const index = todos.findIndex((t) => t.id === id);
if (index !== -1) {
todos.splice(index, 1);
}
return { success: true };
}
3. Головний компонент App.js
import React, { useState, useEffect } from 'react';
import { getTodos, addTodo, toggleTodo, deleteTodo } from './api';
import TodoList from './components/TodoList';
import AddTodoForm from './components/AddTodoForm';
function App() {
const [todos, setTodos] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const data = await getTodos();
setTodos(data);
} catch (err) {
setError('Не вдалося завантажити завдання');
} finally {
setLoading(false);
}
}
fetchData();
}, []);
const handleAddTodo = async (text) => {
try {
const newTodo = await addTodo(text);
setTodos((prev) => [...prev, newTodo]);
} catch (err) {
setError('Помилка при додаванні завдання');
}
};
const handleToggleTodo = async (id) => {
try {
const updated = await toggleTodo(id);
setTodos((prev) =>
prev.map((t) => (t.id === updated.id ? updated : t))
);
} catch (err) {
setError('Помилка при зміні статусу завдання');
}
};
const handleDeleteTodo = async (id) => {
try {
await deleteTodo(id);
setTodos((prev) => prev.filter((t) => t.id !== id));
} catch (err) {
setError('Помилка при видаленні завдання');
}
};
if (loading) return <p>Завантаження...</p>;
if (error) return <p>{error}</p>;
return (
<div>
<h1>Мій TODO-додаток</h1>
<AddTodoForm onAddTodo={handleAddTodo} />
<TodoList
todos={todos}
onToggle={handleToggleTodo}
onDelete={handleDeleteTodo}
/>
</div>
);
}
export default App;
Ключові моменти:
useEffect()
– завантаження списку завдань під час старту додатку.
- Обробка помилок через
try/catch
.
- Окремі функції для додавання, видалення та зміни статусу завдання.
4. Компонент AddTodoForm
import React, { useState } from 'react';
function AddTodoForm({ onAddTodo }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
onAddTodo(text);
setText('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Нове завдання"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Додати</button>
</form>
);
}
export default AddTodoForm;
Що тут демонструє?
- Роботу з формою
- Роботу зі станом (text)
- Передачу callback
onAddTodo
5. Компонент TodoList і TodoItem
import React from 'react';
import TodoItem from './TodoItem';
function TodoList({ todos, onToggle, onDelete }) {
if (!todos.length) {
return <p>Завдань поки що немає</p>;
}
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</ul>
);
}
export default TodoList;
import React from 'react';
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>Видалити</button>
</li>
);
}
export default TodoItem;
Що тут демонструє?
- Розділення логіки між компонентами
- Використання callback’ів для зміни стану (toggle, delete)
Чому таке завдання демонструє ключові навички?
- Робота зі станом (state management)
React-хуки (useState, useEffect) дають уявлення, як розробник керує станом.
- Взаємодія з API
Показує, як обробляються асинхронні запити, помилки та завантаження.
- Обробка подій
Форма додавання і чекбокси – класичні приклади.
- Оптимізація
Хоча у прикладі немає складної оптимізації, але можна побачити, чи відокремлює розробник компоненти, аби уникати зайвих рендерів.
- Обробка помилок
Наявність try/catch
для запитів до API.
- Побудова UI
Використання простих компонентів (список, кнопки, поля вводу).
Можливі покращення та питання на співбесіді
- Сортування чи фільтрація
Додати функціонал сортування за станом (виконані/невиконані).
- Валідація
Перед додаванням завдання можна перевіряти довжину тексту.
- Оптимізація рендеру
Використання React.memo для TodoItem
.
- Тестування
Запитати, як кандидат писав би юніт-тести.
- Альтернативні рішення (Redux, Context)
Як розподіляти логіку, якщо програма стає більшою?
Висновок
Одне завдання з TODO-додатком покриває багато аспектів React: від роботи зі станом і API до структурованого коду й обробки помилок. На співбесіді воно дає змогу швидко зрозуміти рівень кандидата, його підхід до архітектури та вміння справлятися з асинхронними сценаріями. Якщо ви готуєтесь до співбесіди – виконайте це завдання самостійно, а якщо ви інтерв’юер – задайте його кандидату та зверніть увагу на деталі реалізації.
JavaScript: Готуємось до live-coding

Live-coding у JavaScript \u2014 це чудова нагода показати свої знання та вміння розв’язувати проблеми в реальному часі. Такі сесії можна зустріти на співбесідах, хакатонах або конференціях, коли від вас вимагають написати код публічно. У цій статті ми обговоримо, як підготуватися до live-coding, які навички варто закріпити та як поводитися під час розв’язання завдання.
1. Що таке live-coding і чому це важливо?
Live-coding \u2014 це процес, коли ви прямо на очах у глядачів (чи то інтерв’юерів, чи глядачів конференції) пишете та запускаєте код, отримуєте результат і пояснюєте, що відбувається. Це стратегічна перевірка:
- Як ви мислите як розробник
- Як розумієте синтаксис та особливості JavaScript
- Як справляєтеся зі стресом і помилками в реальному часі
Якщо ви претендуєте на посаду, де JavaScript грає важливу роль, live-coding є поширеним інструментом під час співбесід.
2. Підготовка: що варто повторити?
2.1 Базові концепції JavaScript
- Типи даних: примітиви (string, number, boolean, null, undefined, symbol, bigint) та об’єкти
- Об’єкти vs. масиви: методи, ітерація, \u201cspread\u201d та \u201crest\u201d-оператори
- this i контекст: наприклад, у звичайних функціях та стрілкових (arrow)
- Замикання (closure): як вони працюють і навіщо потрібні
- Асинхронне програмування: проміси, async/await, колбеки
2.2 Алгоритми та структури даних
У live-coding можуть попросити реалізувати:
- Сортування
- Пошук у масиві
- Роботу зі структурами на кшталт стека, черги або дерево
Найпоширеніші алгоритми:
- Bubble sort, Quick sort, Merge sort
- Binary search
- DFS (пошук у глибину) i BFS (пошук у ширину)
2.3 Особливості JavaScript
- Hoisting (підняття змінних і функцій)
- Event loop: як виконується асинхронний код?
- Prototype i prototypical inheritance
- ES6+ фічі: деструктуризація, spread/rest, let/const, стрілкові функції
3. Практика перед live-coding
3.1 \u201cCode Kata\u201d
- LeetCode, Codewars, HackerRank \u2014 оберіть платформу, де можна тренуватися.
- Відтворюйте реальний сценарій: встановіть таймер, говоріть вголос, що робите.
3.2 Пояснюйте вголос (think aloud)
- Ваші думки під час коду \u2014 це те, що інтерв’юер або глядачі хочуть почути.
- Пояснюйте, чому обрали такий алгоритм або структуру даних, як поводиться змінна \u201cthis\u201d, чому використовуєте \u201cspread\u201d тощо.
4. Поради під час live-coding
4.1 Структуруйте час
- Прочитайте завдання: упевніться, що ви все зрозуміли.
- Сплануйте: 1\u20132 хвилини на роздуми, пишіть на папері базовий план.
- Починайте з простого: спочатку напишіть найпростіше рішення (навіть якщо воно не оптимальне).
- Покращуйте й оптимізуйте: якщо лишається час.
4.2 Пояснюйте, чому ви робите той чи інший крок
- Це демонструє, що ви мислите як інженер, а не просто \u201cшкрябаєте\u201d код.
- Покажіть, що розумієте часова складність (Big O), пам’ять, структурні особливості JavaScript.
4.3 Обробляйте помилки спокійно
- Якщо виникає помилка, \u201cdebug\u201d \u2014 вмикайте console.log чи breakpoints.
- Думайте вголос: \u201cЗараз перевіримо, чи цей масив взагалі містить елементи\u201d.
- Швидко знаходьте і виправляйте.
4.4 Використовуйте мінімальну допомогу IDE (якщо це дозволено)
- Лише автодоповнення \u2014 не більше.
- Продемонструйте, що знаєте синтаксис і не покладаєтеся повністю на AI-агентів.
5. Типові сценарії завдань у live-coding з JavaScript
- Знайти дублікати в масиві
- Дано масив чисел, знайти повторювані елементи.
- Можна використати об’єкт або \u201cSet\u201d для визначення повторень.
- Розгорнути рядок (reverse string)
function reverseString(str) {
return str.split('').reverse().join('');
}
- Зробити простий запит на API з \u201cfetch\u201d або \u201caxios\u201d
- Обробити отримані дані та відсортувати.
- Створити просту функцію, що рахуватиме факторіал
- Перевірити вхідні дані (наприклад, чи не від’ємне число).
- Пошук найкоротшого слова в реченні
- Розділити рядок за пробілами, зберегти мінімальну довжину слова.
6. Організація коду
6.1 Модульність
Розбивайте код на функції. Не пишіть все у \u201cmain\u201d чи одному файлі. Це покращує читабельність і полегшує відлагодження.
6.2 Коментарі
Короткі, але інформативні коментарі пояснюють суть. Не дублюйте код, а коментуйте ваш підхід.
6.3 Тести
Якщо час дозволяє, пишіть невеличкі тести, наприклад:
console.assert(reverseString('test') === 'tset', 'Функція reverseString працює невірно');
7. Робота з AI-агентами
Якщо live-coding дозволяє використовувати AI IDE-агентів (наприклад, GitHub Copilot), будьте обережні:
- Не сліпо приймайте їхні пропозиції \u2014 аналізуйте, чи не пропонують вони неефективний або неправильний код.
- Якщо під час інтерв’ю це заборонено, відключіть агентів, щоб не порушувати правила.
Висновок
Live-coding \u2014 це не лише перевірка ваших знань JavaScript, а й демонстрація вмінь з аналітики, відлагодження, комунікації. До нього варто заздалегідь готуватися:
- Повторити базові концепції JS (замикання, this, async/await).
- Тренувати алгоритми (Codewars, LeetCode).
- Звикнути пояснювати свої дії вголос \u2014 це показує ваш спосіб мислення.
- Практикувати debug та refactoring.
Зрештою, хороший live-coding \u2014 це поєднання ваших технічних навичок і вміння імпровізувати під час кодування. Бажаємо успіху та впевненості на будь-якій сесії live-coding!