Junior: питання на співбесіді з теми «Рендеринг»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
JSX - синтаксичне розширення JavaScript, що дозволяє писати розмітку прямо в коді. Браузер його не розуміє: компілятор (Babel, esbuild, SWC, TypeScript) перетворює JSX на виклики функцій.
const element = <h1 className="title">Привіт, {user.name}</h1>;
// після компіляції (новий JSX transform)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { className: 'title', children: ['Привіт, ', user.name] });
Результат - звичайний об'єкт-опис («React element»), а не DOM-вузол. React потім вирішує, як привести DOM у відповідність.
Відмінності від HTML:
classNameзамістьclass,htmlForзамістьfor- бо це JavaScript-властивості.- Атрибути в camelCase:
onClick,tabIndex;style- об'єкт:style={{ marginTop: 8 }}. - Кожен тег закривається:
<img />,<br />. - Компонент повертає один корінь. Для кількох елементів - фрагмент
<>...</>. - У фігурних дужках - вирази, не інструкції:
{isAdmin && <Badge />},{items.map(...)}, але неifчиfor.
Безпека: значення в {} автоматично екрануються, тож рядок з <script> виведеться як текст. Небезпечний лише явний dangerouslySetInnerHTML.
Компонент - функція, що повертає JSX. Назва з великої літери обов'язкова: <profile /> React вважатиме HTML-тегом, <Profile /> - компонентом.
Під час оновлення React порівнює новий список елементів зі старим. key каже, який елемент нового списку відповідає якому елементу старого - щоб зберегти його стан і DOM.
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
Без key React виводить попередження й зіставляє елементи за позицією.
З індексом як key - те саме зіставлення за позицією, лише без попередження. Поки список лише дописують у кінець, проблем немає. Після видалення, вставки на початок чи сортування:
- стан компонентів (введений текст у полі, чекбокс, розгорнутість) «переїжджає» до сусіднього елемента;
- React оновлює вміст багатьох елементів замість переміщення одного.
Правила для key:
- Стабільний: той самий для того самого елемента між рендерами -
idз бази. - Унікальний серед сусідів (не глобально).
- Не генерувати під час рендеру:
key={Math.random()}чиcrypto.randomUUID()змушують React щоразу перестворювати всі елементи й губити стан. Якщо в даних немає ID, його генерують один раз - при створенні запису.
Корисний прийом: зміна key компонента примусово перестворює його з чистим станом: <Profile key={userId} userId={userId} /> - форма скинеться при переході до іншого користувача.
У JSX немає директив на кшталт v-if - умови пишуть звичайним JavaScript.
Тернарний оператор - один із двох варіантів:
{isLoggedIn ? <UserMenu /> : <LoginButton />}
&& - показати або нічого:
{hasError && <ErrorMessage />}
Ранній return - для цілих станів компонента:
if (isLoading) return <Spinner />;
if (!user) return null; // null - нічого не рендерити
return <Profile user={user} />;
Пастка з && і числами:
{items.length && <List items={items} />}
Якщо масив порожній, items.length дорівнює 0. Оператор && повертає ліву частину, коли вона хибна, - тобто 0. А React рендерить число 0 як текст. Користувач бачить загадковий «0» на сторінці.
React не рендерить лише null, undefined, false, true і порожній рядок. Число 0 і NaN він показує.
Як правильно:
{items.length > 0 && <List items={items} />}
{!!items.length && <List items={items} />}
{items.length ? <List items={items} /> : null}
Ліва частина && має бути логічним значенням, а не просто «чимось, що буває хибним».
Інші поради:
- складні умови - у змінну чи окремий компонент, а не вкладені тернарні оператори в JSX;
- мапа станів замість ланцюжка умов:
const content = {
loading: <Spinner />,
error: <ErrorMessage />,
empty: <EmptyState />,
}[status] ?? <List items={items} />;
- умова змінює дерево: якщо в гілках однакові компоненти на тому самому місці, React може зберегти їхній стан між гілками. Коли стан має скидатися, різні гілки отримують різний
key.
Приховати, а не прибрати - hidden чи CSS-клас: компонент і його стан лишаються. У React 19.2+ для цього є й <Activity mode="hidden">.
Props - аргументи компонента. Батько передає їх як атрибути JSX, дитина отримує одним об'єктом (зазвичай з деструктуризацією):
function Avatar({ user, size = 48 }) {
return <img src={user.avatarUrl} alt={user.name} width={size} height={size} />;
}
<Avatar user={currentUser} size={64} />
Правила props:
- лише для читання - компонент не змінює свої props. Якщо значення має змінюватися, це стан (у батька чи в самому компоненті);
- значення за замовчуванням - у деструктуризації (
size = 48); спрацьовує дляundefined, але не дляnull; - розгортання
<Avatar {...props} />зручне для обгорток, але приховує, що саме передається, - використовувати помірно.
children - вміст між тегами компонента:
function Card({ title, children }) {
return (
<section className="rounded border p-4">
<h2>{title}</h2>
{children}
</section>
);
}
<Card title="Профіль">
<Avatar user={user} />
<p>{user.bio}</p>
</Card>
Композиція замість успадкування. У React не створюють class SpecialCard extends Card. Спеціалізація - через props і вкладення:
- «слоти» - кілька props з JSX:
<Layout sidebar={<Nav />} header={<Header />}>...</Layout>; - спеціалізований компонент рендерить загальний з потрібними props:
function WarningCard(props) { return <Card {...props} tone="warning" />; }; - поведінку перевикористовують через власні хуки, а не базові класи.
Чому це краще: компоненти незалежні, їх легко комбінувати в будь-якому порядку, а зміни в «базовому» компоненті не ламають ієрархію нащадків.
Корисний наслідок children для продуктивності: якщо дорогий компонент передано як children, батько, що змінює свій стан, не перерендерює його - JSX-елемент створено вище й не змінився.
Prop drilling - коли дані передаються через багато рівнів, які їх не використовують. Спершу варто спробувати композицію (передати готовий JSX вниз), а вже потім Context.
Компонент повертає один кореневий елемент JSX. Щоб повернути кілька елементів без зайвої обгортки в DOM, використовують Fragment:
function UserInfo({ user }) {
return (
<>
<dt>Ім'я</dt>
<dd>{user.name}</dd>
</>
);
}
<>...</> - скорочений запис <Fragment>...</Fragment>. У DOM потрапляють лише dt і dd.
Навіщо не обгортати в <div>:
- валідний HTML: усередині
<dl>,<ul>,<table>,<tr>допустимі лише певні елементи.<div>між<tr>і<td>- некоректна розмітка й попередження React; - CSS-розкладка: у flex- чи grid-контейнері зайвий
<div>стає окремим елементом сітки й ламає розташування; - менше DOM - трохи легше для браузера на великих сторінках.
Fragment з key - коли фрагмент рендериться в циклі. Скорочений запис <> атрибутів не приймає, потрібна повна форма:
import { Fragment } from 'react';
function Glossary({ items }) {
return (
<dl>
{items.map((item) => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
);
}
Що варто знати:
key- єдиний атрибут, який приймає Fragment (в експериментальних версіях з'являється щеref);- Fragment не має власного DOM-вузла - на нього не можна повісити обробник події чи клас;
- збереження стану: React однаково трактує
<>з дітьми і масив дітей на верхньому рівні, тож перехід між<><Child /></>і<Child />не скидає стан. А от зміна позиції в дереві чиkey- скидає.
Повернути масив (return [<li key="a" />, <li key="b" />]) теж можна, але потрібні key на кожному елементі - Fragment читається простіше.