Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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 /> - компонентом.

Докладніше в документації: Розмітка з JSX

Під час оновлення 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} /> - форма скинеться при переході до іншого користувача.

Докладніше в документації: Рендер списків: key

У 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.

Докладніше в документації: Передача props компоненту

Компонент повертає один кореневий елемент 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 читається простіше.

Докладніше в документації: Fragment