Перейти к основному содержимому

Middle JavaScript и React

Экспресс-шпаргалка 20/20

  1. React сравнивает предыдущее и новое virtual DOM дерево и применяет в real DOM только минимально необходимые изменения.
  2. key нужен, чтобы React стабильно идентифицировал элементы между рендерами и корректно обновлял список.
  3. В зависимости нужно включать все внешние значения, которые используются внутри effect, чтобы избежать stale данных.
  4. Они полезны, когда есть измеримая проблема производительности: дорогие вычисления или лишние ререндеры из-за нестабильных ссылок.
  5. Контролируемая форма хранит значение в state, неконтролируемая — в DOM через ref.
  6. State поднимают к ближайшему общему родителю, если нескольким компонентам нужны одни и те же данные.
  7. При изменении value контекста все подписанные потребители могут перерендериться.
  8. Error Boundary ловит ошибки рендера в дочернем дереве React-компонентов и показывает fallback UI.
  9. Для code splitting и отложенной загрузки тяжелых частей интерфейса.
  10. Нестабильные props (функции/объекты), слишком высокий state, изменения контекста и отсутствие мемоизации в нужных местах.
  11. CSR рендерит в браузере, SSR рендерит HTML на сервере на каждый запрос, SSG генерирует HTML заранее на этапе сборки.
  12. Hydration mismatch — несоответствие между HTML с сервера и первым рендером клиента.
  13. Оба описывают форму данных, но interface проще расширять через declaration merging, а type гибче для union/intersection и утилитарных комбинаций.
  14. Generics позволяют писать переиспользуемые функции/типы с сохранением строгой типизации.
  15. Это union объектов с общим дискриминатором (например, type), который позволяет TypeScript безопасно сужать тип.
  16. Чаще всего Partial, Pick, Omit, Record, Readonly, ReturnType.
  17. Promise.all падает на первой ошибке, Promise.allSettled дожидается завершения всех промисов и возвращает статус каждого.
  18. AbortController позволяет отменять fetch и другие abortable операции, чтобы не держать лишние запросы и состояния.
  19. Это обработка событий на общем родителе вместо подписки на каждый дочерний элемент.
  20. Tree shaking удаляет неиспользуемые экспорты из бандла, если код и сборка позволяют это статически определить.

1. Как работает reconciliation в React?

Теги: react, reconciliation, rendering, performance Сложность: Middle

Короткий ответ

Reconciliation - это сопоставление прошлого и нового React-дерева, после которого React решает, что обновить, сохранить или размонтировать.

Пояснение

В реальных frontend-интервью этот вопрос проверяет не знание слова "virtual DOM", а понимание identity компонента. React хранит state не "в JSX", а в позиции компонента в дереве. Если на той же позиции остается тот же тип компонента, React обычно сохраняет state и обновляет props. Если меняется тип или key, старое поддерево считается другим: cleanup эффектов, remount, потеря локального state, повторная инициализация.

Главная практическая мысль: rerender не равен remount. Rerender - это повторный вызов компонента для расчета нового UI. Remount - это уничтожение старого экземпляра и создание нового. На интервью сильный кандидат объясняет, как это проявится в форме, таблице, списке, модалке или wizard flow, а не только говорит "React сравнивает деревья".

Что сказать на интервью (30-60 секунд)

  1. Я бы начал с identity: React сопоставляет элементы по типу, позиции в дереве и key.
  2. Если identity сохранилась, React обновляет props/state и идет глубже; если identity изменилась, поддерево сбрасывается.
  3. Поэтому важно различать rerender, DOM update и remount: это разные события с разной ценой.
  4. На практике я проверяю reconciliation через render/remount trace, особенно в формах, списках и wizard flows.
  5. Красный флаг - случайные key, условные wrapper-компоненты и вложенные component definitions, которые случайно сбрасывают state.

Мини-пример

function CheckoutStep({ step }) {
return step === 'delivery'
? <DeliveryForm />
: <PaymentForm />;
}

Если пользователь вводил данные в DeliveryForm, а UI переключился на другой тип компонента в той же позиции, старый form state будет уничтожен. Если нужно сохранить draft между шагами, state должен жить выше или сохраняться отдельно.

Углубление (2-3 минуты)

  1. Production-кейс: в checkout wizard пользователь заполнил адрес, потом переключил способ доставки. Из-за смены key у wrapper-компонента форма размонтировалась, draft пропал, и пользователь вынужден повторно вводить данные.
  2. Edge-case: key={Math.random()} делает каждый render новым деревом, поэтому React пересоздает DOM, сбрасывает input и заново запускает эффекты.
  3. Edge-case: изменение props у того же компонента не сбрасывает state само по себе; если нужен reset, его делают явно через key или controlled state.
  4. Readiness: вы можете объяснить, где state сохранится, где сбросится, и какой user-visible bug появится.

Практика

Сделайте render trace для Parent -> List -> Row: action, component, rendered, mounted/unmounted, state preserved, why. Отдельно добавьте сценарий с key={Math.random()} и объясните, почему он ломает ввод.

Типичные ошибки

  1. Говорят, что virtual DOM "быстрее real DOM" без объяснения сопоставления деревьев.
  2. Путают rerender, DOM update и remount.
  3. Ставят случайный key или индекс там, где порядок списка меняется.
  4. Не могут объяснить, почему state формы исчезает при смене key.

Follow-up вопросы

  1. Чем отличается rerender от remount?
  2. Что будет со state дочернего компонента, если поменять его key?
  3. Почему React не может надежно сопоставить элементы списка без стабильного key?
  4. Как бы вы нашли лишние remount в форме или таблице?

Что повторить

  1. React lesson 1: render, reconciliation, key.
  2. Мосты практики: React rendering and effects.

Связанные модули и карта

  1. Карта подготовки: React

2. Зачем нужен key в списках?

Теги: react, key, lists, state-preservation Сложность: Middle

Короткий ответ

key нужен, чтобы React узнавал один и тот же элемент списка между рендерами, даже если элементы добавили, удалили, отфильтровали или отсортировали.

Пояснение

key - это не "ускоритель map" и не props для компонента. Это подсказка React внутри массива siblings: какой новый элемент соответствует какому старому элементу. Если ключ стабильный и взят из данных, React может сохранить локальный state нужной строки. Если ключ основан на индексе в изменяемом списке, React может связать старый state не с тем item.

Типичный интервью-паттерн: интервьюер дает список с input внутри строки, потом просит удалить первый элемент или отсортировать список. Слабый ответ - "индекс нельзя". Сильный ответ - показать, как state/focus/draft переезжает к другому item и когда индекс все-таки безопасен.

Что сказать на интервью (30-60 секунд)

  1. key должен выражать identity элемента в данных: todo.id, message.id, user.id.
  2. Он уникален только среди соседей одного массива, а не глобально во всем приложении.
  3. Индекс как key опасен, когда список меняет порядок, фильтруется, пополняется или содержит локальный state.
  4. Если список статичный, не сортируется и не имеет stateful children, индекс почти не несет риска.
  5. Я всегда проверяю key через сценарий: удалить первый item, отсортировать список, отредактировать input в середине.

Мини-пример

tasks.map(task => (
<TaskRow key={task.id} task={task} />
));

Если вместо task.id использовать индекс, после сортировки draft text, focus или animation state могут остаться на позиции строки, но уже относиться к другой задаче.

Углубление (2-3 минуты)

  1. Production-кейс: оператор редактирует цену второй позиции заказа. В это время список автоматически сортируется по скидке. При key={index} введенное значение может визуально оказаться у другой позиции.
  2. Edge-case: короткий список статичных пунктов меню без reorder, filter, insert и локального state можно рендерить с индексом, но это должно быть осознанное ограничение.
  3. Edge-case: key не приходит в child как prop; если нужен id внутри TaskRow, его надо передать отдельно.
  4. Readiness: вы можете не только сказать "индекс плохо", а доказать, какой state сохранится неправильно.

Практика

Нарисуйте таблицу для списка с тремя строками и input: old index, old id, draft value, new index after delete/sort, which row receives draft. Повторите таблицу для key={id} и key={index}.

Типичные ошибки

  1. Считают key способом "ускорить map", а не identity для reconciliation.
  2. Используют Math.random() или Date.now() как key.
  3. Используют индекс в сортируемой таблице, форме, drag-and-drop или списке уведомлений.
  4. Не могут назвать условия, при которых индекс допустим.

Follow-up вопросы

  1. Почему key должен быть уникален только среди siblings, а не глобально во всем приложении?
  2. Что произойдет при key={index}, если удалить первый элемент списка?
  3. Где должен стоять key: внутри child-компонента или в map?
  4. Когда индекс как key приемлем?

Что повторить

  1. React lesson 1: списки, key, сохранение state.
  2. Interview pattern taxonomy: render-ownership.

Связанные модули и карта

  1. Карта подготовки: React

3. Как правильно работать с зависимостями в useEffect?

Теги: react, useeffect, dependencies, async Сложность: Middle

Короткий ответ

В dependencies нужно указывать все reactive значения из компонента, которые effect читает: props, state и функции/переменные из render scope.

Пояснение

useEffect нужен для синхронизации с внешним миром: сеть, subscription, timer, DOM API, сторонний widget. Dependency array не является ручным расписанием "когда хочу, тогда запускаю"; он должен соответствовать коду внутри effect. Если effect читает query, userId, token или callback, а dependency отсутствует, effect работает со старым snapshot значения.

В интервью обычно проверяют два риска. Первый - stale closure: effect видит старые данные. Второй - cleanup: старый запрос, таймер или подписка продолжает жить после изменения входных данных. Поэтому хороший ответ всегда включает не только dependencies, но и отмену/игнорирование старого результата.

Что сказать на интервью (30-60 секунд)

  1. Я сначала спрашиваю: effect точно нужен или это derived value, который можно посчитать во время render?
  2. Если effect синхронизирует внешний ресурс, dependencies должны отражать все значения, которые он читает.
  3. Для async важно не только перезапустить effect, но и отменить или проигнорировать устаревший результат.
  4. [] подходит только когда effect действительно не зависит от меняющихся props/state, а не как способ отключить линтер.
  5. На практике я доказываю корректность через сценарий быстрых изменений: a -> ab -> abc, старый ответ приходит последним.

Мини-пример

useEffect(() => {
const controller = new AbortController();

fetchUser(userId, { signal: controller.signal }).then(setUser);

return () => controller.abort();
}, [userId]);

Здесь userId в dependencies обязателен: при смене пользователя старый запрос отменится, а новый effect запросит актуальные данные.

Углубление (2-3 минуты)

  1. Production-кейс: autocomplete отправляет запросы на a, ab, abc. Ответ на a приходит позже всех и перетирает экран устаревшими результатами.
  2. Edge-case: если callback создается на каждом render и попадает в dependencies, effect может запускаться слишком часто. Решение - перенести функцию внутрь effect, стабилизировать callback или убрать лишний effect.
  3. Edge-case: subscription и interval требуют cleanup, иначе после смены страницы останутся лишние listeners или таймеры.
  4. Readiness: вы можете назвать stale переменную, момент запуска cleanup и способ защиты от устаревшего async результата.

Практика

Сделайте dependency audit для компонента с query, token, setTimeout, fetch и window.addEventListener. Для каждой строки заполните: external value, dependency needed, cleanup, stale failure, fix.

Типичные ошибки

  1. Убирают dependency, чтобы "не было лишнего запроса", и получают stale данные.
  2. Кладут в effect derived state, который можно вычислить во время render.
  3. Не чистят subscription, interval, timeout или network request.
  4. Лечат двойной запуск в development через useRef, но не исправляют cleanup.

Follow-up вопросы

  1. Почему нельзя просто удалить query из dependencies?
  2. Когда cleanup запускается относительно следующего effect?
  3. Что выбрать для stale response: AbortController, requestId или проверку актуального значения?
  4. Какие вычисления лучше оставить в render, а не переносить в useEffect?

Что повторить

  1. React lesson 2: lifecycle effect, cleanup, dependencies.
  2. React effect/state debug pack: stale response drill.

Связанные модули и карта

  1. Карта подготовки: React

4. Когда useMemo и useCallback реально полезны?

Теги: react, usememo, usecallback, optimization Сложность: Middle

Короткий ответ

useMemo и useCallback полезны, когда есть измеримая цена: дорогое вычисление, memoized child, стабильная dependency или большой список.

Пояснение

Эти хуки не "ускоряют React" автоматически. useMemo сохраняет результат вычисления между render, пока dependencies те же. useCallback сохраняет ссылку на функцию. Польза появляется только если эта стабильность где-то используется: например, тяжелый filter не должен пересчитываться на каждый keypress, или memoized row не должен получать новый onSelect без изменения данных.

Сильный ответ на интервью начинается не с "я оборачиваю все в memo", а с диагностики: что именно дорого, как это измерили, какой render лишний, какой dependency нестабилен, и какой UX-баг или latency это создает.

Что сказать на интервью (30-60 секунд)

  1. Я использую memoization после baseline: Profiler, render count или простой замер времени вычисления.
  2. useMemo - для результата дорогого вычисления; useCallback - для стабильной ссылки, если consumer реально сравнивает ссылку.
  3. Если child не memoized и callback никуда не уходит как dependency, useCallback обычно не дает пользы.
  4. Если dependency каждый render новый, memo будет инвалидироваться каждый render.
  5. Главный риск - stale dependencies и усложнение кода ради микропользы.

Мини-пример

const filtered = useMemo(() => heavyFilter(items, query), [items, query]);
const onSelect = useCallback(id => setSelected(id), []);

Этот пример имеет смысл, только если heavyFilter действительно дорогой или onSelect передается в memoized rows. Иначе это может быть лишней сложностью.

Углубление (2-3 минуты)

  1. Production-кейс: CRM-таблица на 2 000 строк фильтруется при каждом вводе символа, а строка содержит форматирование валюты, статусы и actions. Без baseline команда спорит "медленно React или API"; с baseline видно, что тормозит client render.
  2. Edge-case: если родитель каждый раз создает новый items.map(...) и передает его ниже, useMemo([items]) внутри ребенка не поможет.
  3. Edge-case: если забыть dependency, UI может показывать старый filtered list, что хуже лишнего render.
  4. Readiness: вы можете сказать, где memo нужен, где ничего не меняет, и чем это доказано.

Практика

Заполните memoization baseline table: operation, input size, current cost, rerender trigger, memo candidate, dependency risk, proof after fix. Не добавляйте memo, пока нет строки proof.

Типичные ошибки

  1. Оборачивают каждую функцию в useCallback без memoized consumer.
  2. Забывают dependency и получают stale value.
  3. Мемоизируют дешевое вычисление, но оставляют высокий state, который ререндерит все дерево.
  4. Ожидают, что useMemo предотвратит render компонента.

Follow-up вопросы

  1. Чем useMemo отличается от React.memo?
  2. Когда useCallback реально нужен?
  3. Почему memo может не сработать, если dependency каждый раз новая?
  4. Что опаснее: лишний render или stale значение из-за неверных dependencies?

Что повторить

  1. React lesson 3: state, derived data, memoization.
  2. Interview pattern taxonomy: performance-budget and render-ownership.

Связанные модули и карта

  1. Карта подготовки: React

5. Контролируемые и неконтролируемые формы в React?

Теги: react, forms, controlled-uncontrolled, validation Сложность: Middle

Короткий ответ

В controlled input source of truth - React state; в uncontrolled input source of truth - DOM, а React читает значение через ref или submit.

Пояснение

Этот вопрос проверяет умение выбрать ownership формы. Controlled подход нужен, когда UI зависит от текущего значения: live validation, маски, disabled submit, зависимые поля, autosave, синхронизация с query/state. Uncontrolled подход проще, когда значение нужно только при submit, когда поле тяжелое для постоянного render или когда API браузера не дает нормально контролировать значение, как у file input.

Сильный ответ не должен звучать как "controlled всегда лучше". В реальном проекте выбор зависит от validation timing, размера формы, UX-стоимости render на каждый keypress и того, где живет business state.

Что сказать на интервью (30-60 секунд)

  1. Я выбираю controlled, если значение должно управлять UI прямо сейчас: validation, mask, disabled button, dependent field.
  2. Я выбираю uncontrolled, если значение нужно считать в конце или если поле лучше оставить под управлением DOM.
  3. Для controlled input обязательны value и onChange, которые синхронно обновляют backing state.
  4. Нельзя переключать input между controlled и uncontrolled в течение жизни компонента.
  5. Риск не в самом подходе, а в рассинхроне source of truth: UI показывает одно, validation читает другое.

Мини-пример

<input value={name} onChange={e => setName(e.target.value)} />

Если убрать onChange, поле станет read-only. Если иногда передавать undefined вместо строки, React может считать input то controlled, то uncontrolled.

Углубление (2-3 минуты)

  1. Production-кейс: форма настроек включает submit только после валидного email и выбранного тарифа. Controlled state делает правила явными и тестируемыми.
  2. Edge-case: <input value={maybeName}>, где maybeName иногда undefined, может дать controlled/uncontrolled warning. Для controlled text input держите строку, например value={name ?? ''}.
  3. Edge-case: checkbox контролируется через checked, а не через value; file input читают через DOM/ref.
  4. Edge-case: большая форма может лагать, если весь экран зависит от каждого keypress; тогда state надо локализовать, разбить форму или использовать deferred подход.
  5. Readiness: вы можете выбрать подход по source of truth, validation timing и UX-cost, а не по привычке.

Практика

Сделайте form failure table для login/settings form: field, controlled/uncontrolled, source of truth, validation timing, failure mode, fix. Обязательно включите text input, checkbox и file input.

Типичные ошибки

  1. Используют value без onChange и получают readonly input.
  2. Передают undefined/null в value и ловят controlled/uncontrolled warning.
  3. Делают всю большую форму controlled, хотя live state нужен только нескольким полям.
  4. Смешивают DOM ref и React state так, что validation читает одно значение, а UI показывает другое.

Follow-up вопросы

  1. Что является source of truth в controlled input?
  2. Почему React ругается на переход controlled -> uncontrolled?
  3. Как обработать file input?
  4. Когда uncontrolled форма лучше controlled?

Что повторить

  1. React lesson 2: forms, effects, controlled inputs.
  2. Мосты практики: React rendering and effects.

Связанные модули и карта

  1. Карта подготовки: React

6. Когда поднимать state вверх (lifting state up)?

Теги: react, state-management, lifting-state, architecture Сложность: Middle

Короткий ответ

State поднимают к ближайшему общему родителю, если нескольким компонентам нужны одни и те же данные.

Что сказать на интервью (30-60 секунд)

  1. State должен жить у ближайшего общего владельца, которому нужно читать или менять это значение.
  2. Если значение нужно только одному input или dropdown, оставляю state локально; если List и Details должны синхронизироваться, поднимаю к их общему parent.
  3. Derived data не обязательно хранить в state: фильтрованный список часто можно вычислить из items и query.
  4. Production-риск: два источника правды дают рассинхрон, а слишком высокий state заставляет ререндерить большое дерево.
  5. Проверяю через state ownership map: state, owner, readers, writers, derived?, rerender cost.

Мини-пример

// Parent хранит selectedId и передает вниз в List и Details

Углубление (2-3 минуты)

  1. Production-сценарий: ProductList и ProductDetails должны видеть один selectedId. Если каждый хранит свой selected state, пользователь видит один товар в списке и другой в деталях.
  2. Edge-case: isDropdownOpen для одной строки не нужно поднимать к странице, иначе открытие dropdown может ререндерить весь список.
  3. Практика: для filterable list заполните ownership map: query, selectedId, items, filteredItems, isRowMenuOpen.
  4. Readiness: вы можете объяснить, что должно быть state, что derived data, а что server/cache data вне локального component state.

Типичные ошибки

  1. Поднимают весь state "на всякий случай" и получают prop drilling с лишними rerender.
  2. Дублируют одно значение в parent и child, а потом синхронизируют через effect.
  3. Хранят derived data в state и забывают обновить его при изменении исходных данных.
  4. Путают state ownership с глобальным store: не каждый shared state должен стать global.

Follow-up вопросы

  1. Как выбрать ближайшего общего владельца state?
  2. Когда derived value лучше не хранить в state?
  3. Почему поднятый слишком высоко state может ухудшить performance?
  4. Как избежать двух источников правды между parent и child?

Что повторить

  1. React lesson 3: state ownership and derived state.
  2. Мосты практики: React state and performance.
  3. Free-practice: state ownership map for filterable list.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

7. Какие проблемы у Context API по производительности?

Теги: react, context-api, performance, state Сложность: Middle

Короткий ответ

При изменении value контекста все подписанные потребители могут перерендериться.

Что сказать на интервью (30-60 секунд)

  1. Context удобен для данных, которые нужны многим веткам: theme, locale, auth session, feature flags.
  2. Когда value provider меняет identity, все consumers этого context получают новый value и могут перерендериться.
  3. useMemo помогает только стабилизировать объект value, но не решает проблему слишком широкого context.
  4. Для часто меняющихся данных лучше split contexts, локальный state ближе к consumer, external store или selector-based подход.
  5. Проверяю через consumer trace: какой provider изменился, какие consumers перерендерились, какой field им реально нужен.

Мини-пример

const value = useMemo(() => ({ theme, setTheme }), [theme]);
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>

Углубление (2-3 минуты)

  1. Production-сценарий: в AppContext лежат theme, user, cart, searchDraft. При каждом keypress в search перерендериваются header, cart badge и страницы, которым search не нужен.
  2. Edge-case: const value = { theme, setTheme } создает новый объект на каждом render provider, даже если theme не изменился.
  3. Практика: сделайте Context consumer trace: provider value, changed field, consumer, needed field, rerender expected?, fix.
  4. Readiness: вы можете выбрать между split context, memoized value, moving state down и external store, а не просто сказать "используйте Redux".

Типичные ошибки

  1. Складывают в один context и редко меняющийся theme, и быстро меняющийся searchDraft.
  2. Думают, что React.memo всегда спасет consumer от изменения context.
  3. Мемоизируют value, но оставляют нестабильные вложенные callbacks или objects.
  4. Используют Context как универсальный global store без границ ownership.

Follow-up вопросы

  1. Что происходит с consumers при изменении value provider?
  2. Почему useMemo(() => ({ theme }), [theme]) не решает проблему частых изменений theme?
  3. Когда split context лучше одного общего context?
  4. Почему Context плохо подходит для rapidly changing input state?

Что повторить

  1. React lesson 4: Context limits and performance.
  2. Interview pattern taxonomy: render-ownership.
  3. Free-practice: Context consumer trace for ThemeContext, AuthContext, SearchContext.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

8. Что такое Error Boundary и где его ограничения?

Теги: react, error-boundary, resilience, monitoring Сложность: Middle

Короткий ответ

Error Boundary ловит ошибки рендера в дочернем дереве React-компонентов и показывает fallback UI.

Что сказать на интервью (30-60 секунд)

  1. Error Boundary ловит ошибки во время render, lifecycle и constructor в дочернем дереве компонентов; сам boundary обычно реализуют class-компонентом.
  2. Он не ловит ошибки в event handlers, async callbacks, setTimeout, rejected promises и серверном рендере.
  3. Boundary нужен, чтобы изолировать падение части UI, показать fallback и отправить ошибку в monitoring.
  4. Размещаю boundaries по пользовательским зонам: виджет, route, checkout step, editor panel, а не только один глобальный wrapper.
  5. Проверяю placement table: area, failure, fallback, reset action, monitoring signal.

Мини-пример

<ErrorBoundary fallback={<ErrorScreen />}>
<App />
</ErrorBoundary>

Углубление (2-3 минуты)

  1. Production-сценарий: recommendations widget падает из-за неожиданных данных. Boundary вокруг widget сохраняет checkout page живой и показывает локальный fallback.
  2. Edge-case: ошибка внутри onClick не попадет в Error Boundary; ее нужно обработать в handler или через async state.
  3. Edge-case: ошибка загрузки lazy chunk обычно требует связки Suspense для loading и Error Boundary для failed import.
  4. Практика: заполните boundary placement table для AppShell, CheckoutStep, ProductReviews, RichTextEditor.
  5. Readiness: вы можете объяснить, какую ошибку boundary поймает, какую нет, и как пользователь восстановится через reset.

Типичные ошибки

  1. Считают Error Boundary заменой try/catch для event handler.
  2. Ставят только один глобальный boundary и теряют весь экран при локальной ошибке.
  3. Показывают fallback без кнопки retry/reset и без сигнала в monitoring.
  4. Не тестируют failed lazy import, хотя именно там пользователи часто видят пустой экран.

Follow-up вопросы

  1. Какие ошибки Error Boundary не ловит?
  2. Где поставить boundary в dashboard из независимых widgets?
  3. Как сбросить boundary после ошибки?
  4. Чем fallback для render error отличается от loading fallback в Suspense?

Что повторить

  1. React lesson 4: resilience boundaries.
  2. Мосты практики: React state and performance.
  3. Free-practice: boundary placement table with fallback and reset behavior.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

9. Когда использовать React.lazy и Suspense?

Теги: react, lazy, suspense, code-splitting Сложность: Middle

Короткий ответ

Для code splitting и отложенной загрузки тяжелых частей интерфейса.

Что сказать на интервью (30-60 секунд)

  1. React.lazy загружает компонент через dynamic import() и выносит его в отдельный chunk.
  2. Suspense показывает fallback, пока lazy component или другая suspense-enabled dependency не готова.
  3. Использую это для тяжелых route-level страниц, вкладок, модалок, редакторов, графиков и редко открываемых настроек.
  4. Не выношу в lazy критический above-the-fold UI, если fallback ухудшит первый экран или добавит сетевой waterfall.
  5. Проверяю lazy chunk decision table: component, initially visible?, size/cost, load trigger, fallback, error handling.

Мини-пример

const Settings = React.lazy(() => import('./Settings'));

<Suspense fallback={<SettingsSkeleton />}>
<Settings />
</Suspense>

Углубление (2-3 минуты)

  1. Production-сценарий: settings modal содержит rich editor и charts. Его можно загружать по клику, чтобы не тащить этот код в initial bundle.
  2. Edge-case: если lazy chunk нужен сразу для hero area, пользователь увидит skeleton вместо основного контента и может ухудшиться perceived load.
  3. Edge-case: failed dynamic import не решается Suspense fallback; нужен Error Boundary рядом.
  4. Практика: заполните lazy chunk decision table для Dashboard, SettingsModal, ChartPanel, CheckoutButton.
  5. Readiness: вы можете объяснить trade-off между меньшим initial JS и дополнительным network request при открытии lazy части.

Типичные ошибки

  1. Lazy-load маленьких компонентов, где overhead запроса больше выгоды.
  2. Ставят fallback, который меняет layout и вызывает визуальный скачок.
  3. Забывают Error Boundary для failed chunk.
  4. Делают lazy import внутри render branch нестабильно вместо module scope.

Follow-up вопросы

  1. Чем React.lazy отличается от обычного static import?
  2. Почему Suspense fallback не обрабатывает ошибку загрузки chunk?
  3. Что лучше lazy-load: route page, modal или кнопку в первом экране?
  4. Как избежать layout shift у fallback?

Что повторить

  1. React lesson 4: code splitting and loading states.
  2. Interview pattern taxonomy: performance-budget.
  3. Free-practice: lazy chunk decision table for one real page.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

10. Какие основные причины лишних ререндеров в React?

Теги: react, rerender, profiler, optimization Сложность: Middle

Короткий ответ

Нестабильные props (функции/объекты), слишком высокий state, изменения контекста и отсутствие мемоизации в нужных местах.

Что сказать на интервью (30-60 секунд)

  1. Компонент ререндерится, когда меняется его state, props, context или когда ререндерится parent и child не защищен memoization.
  2. Частые причины: высокий state, нестабильные object/function props, широкий context, дорогой derived data in render, неправильные key.
  3. Не каждый rerender проблема: проблема начинается, когда он дорогой, частый или ломает UX.
  4. Диагностика идет от evidence: React DevTools Profiler, render trace, список props/context changes, затем минимальный fix.
  5. Fix выбираю по причине: move state down, split context, stabilize props, React.memo, useMemo, useCallback, virtualization.

Мини-пример

const onClick = () => setOpen(true); // новая ссылка каждый render

Углубление (2-3 минуты)

  1. Production-сценарий: ввод в search field ререндерит таблицу на 2 000 строк, потому что query лежит в page state, а rows получают новый onSelect на каждый render.
  2. Edge-case: если child дешевый и не memoized, useCallback для handler может ничего не улучшить.
  3. Edge-case: изменение key может выглядеть как rerender, но фактически это remount и сброс state.
  4. Практика: заполните rerender diagnosis table: interaction, component, trigger, evidence, fix, risk.
  5. Readiness: вы можете назвать причину конкретного rerender и выбрать targeted fix, а не добавлять memo everywhere.

Типичные ошибки

  1. Оптимизируют все ререндеры, даже дешевые и редкие.
  2. Добавляют useCallback без React.memo или другого потребителя стабильной ссылки.
  3. Не замечают, что context value меняется и пробивает memoized children.
  4. Лечат rerender мемоизацией, хотя правильнее опустить state ниже.

Follow-up вопросы

  1. Какие четыре события чаще всего запускают rerender?
  2. Чем rerender отличается от remount?
  3. Когда React.memo не поможет?
  4. Как понять, что state нужно опустить ниже, а не мемоизировать callbacks?

Что повторить

  1. React lesson 3: state, memoization and derived data.
  2. Interview pattern taxonomy: render-ownership and performance-budget.
  3. Free-practice: rerender diagnosis table for filterable list or dashboard widgets.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

11. Разница между CSR, SSR и SSG?

Теги: frontend, csr-ssr-ssg, rendering-strategy, seo Сложность: Middle

Короткий ответ

CSR рендерит в браузере, SSR рендерит HTML на сервере на каждый запрос, SSG генерирует HTML заранее на этапе сборки.

Что сказать на интервью (30-60 секунд)

  1. CSR отдает почти пустой HTML и собирает UI в браузере; сильная сторона - интерактивные кабинеты, слабая - первый контент и SEO без дополнительных мер.
  2. SSR генерирует HTML на сервере на каждый запрос; полезен для динамического контента, но добавляет server cost, cache strategy и риск hydration issues.
  3. SSG генерирует HTML на build time; хорош для стабильных страниц, документации и маркетинга, но требует стратегии обновления данных.
  4. Выбор зависит от freshness, SEO, personalized data, first paint, cacheability and runtime cost.
  5. Проверяю через rendering strategy decision table: page, data freshness, SEO, personalization, render mode, fallback/update strategy.

Мини-пример

Next.js: static page + dynamic route with server rendering

Углубление (2-3 минуты)

  1. Production-сценарий: публичная статья подходит для SSG, каталог с ценами может требовать SSR или revalidation, личный кабинет часто остается CSR после auth.
  2. Edge-case: страница может быть статичной по shell, но загружать персональные данные на клиенте после auth.
  3. Edge-case: SSR не делает страницу интерактивной сам по себе; после HTML всё равно нужна hydration.
  4. Практика: заполните decision table для landing, blog article, product page, user dashboard, admin settings.
  5. Readiness: вы можете объяснить не "SSR быстрее", а какой trade-off вы выбираете для freshness, SEO, cache и interactivity.

Типичные ошибки

  1. Называют SSR универсально лучшим вариантом без учета cache и server cost.
  2. Путают "HTML есть на сервере" и "страница уже интерактивна".
  3. Выбирают SSG для данных, которые должны быть актуальны на каждый запрос, без revalidation plan.
  4. Переносят private user data в HTML, который может быть закеширован неправильно.

Follow-up вопросы

  1. Чем SSR отличается от SSG по моменту генерации HTML?
  2. Почему CSR может быть нормальным выбором для dashboard?
  3. Что должно быть true, чтобы SSG был безопасным для страницы?
  4. Где появляется hydration в SSR-приложении?

Что повторить

  1. Rendering lesson 1: rendering strategies and critical path.
  2. Мосты практики: HTML, CSS and rendering.
  3. Free-practice: rendering strategy decision table for five page types.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

12. Что такое hydration mismatch и как его избежать?

Теги: frontend, hydration, ssr, rendering Сложность: Middle

Короткий ответ

Hydration mismatch — несоответствие между HTML с сервера и первым рендером клиента.

Что сказать на интервью (30-60 секунд)

  1. Hydration ожидает, что серверный HTML совпадет с первым клиентским render.
  2. Mismatch появляется из-за nondeterministic values: Date.now(), Math.random(), locale/timezone differences, browser-only state, auth state, viewport checks.
  3. Исправление: сделать первый render deterministic, перенести browser-only часть в effect, передать initial data с сервера или явно разделить client-only UI.
  4. Production-риск: пользователь видит warning, UI пересобирается, может мигнуть текст, потеряться focus или сломаться обработчики.
  5. Проверяю через hydration audit: server value, first client value, source of difference, fix, verification.

Мини-пример

// Плохо: рендерим Date.now() напрямую в SSR markup

Углубление (2-3 минуты)

  1. Production-сценарий: сервер рендерит "0 товаров", а клиент сразу читает cart из localStorage и рендерит "3 товара"; первый клиентский render не совпадает с HTML.
  2. Edge-case: new Date().toLocaleString() может дать разный результат на сервере и клиенте из-за timezone/locale.
  3. Edge-case: проверка window.innerWidth во время render невозможна на сервере и должна быть отложена или заменена CSS/responsive approach.
  4. Практика: сделайте hydration mismatch audit для Date.now(), localStorage cart, theme from system, viewport-dependent menu.
  5. Readiness: вы можете объяснить, почему useEffect меняет UI уже после hydration, а не исправляет первый HTML.

Типичные ошибки

  1. Рендерят случайные значения или текущую дату прямо в SSR markup.
  2. Читают window, document, localStorage во время render.
  3. Скрывают warning, не устраняя несовпадение первого render.
  4. Дублируют логику форматирования даты на сервере и клиенте с разными locale/timezone settings.

Follow-up вопросы

  1. Почему Date.now() в render опасен для SSR?
  2. Как безопасно показать значение из localStorage?
  3. Чем client-only component отличается от deterministic SSR render?
  4. Как проверить, что hydration warning исчез не случайно?

Что повторить

  1. Rendering lesson 2: hydration and browser/client boundary.
  2. Мосты практики: HTML, CSS and rendering.
  3. Free-practice: hydration mismatch audit for four nondeterministic values.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

13. type vs interface в TypeScript?

Теги: typescript, type-vs-interface, api-contracts, styleguide Сложность: Middle

Короткий ответ

Оба описывают форму данных, но interface проще расширять через declaration merging, а type гибче для union/intersection и утилитарных комбинаций.

Что сказать на интервью (30-60 секунд)

  1. interface хорошо подходит для описания object shape, public contracts, props and extendable models.
  2. type нужен для union, intersection, mapped/conditional types, tuples и примитивных aliases.
  3. В проекте важнее consistency и styleguide, чем спор "всегда type" или "всегда interface".
  4. На API boundary типы не валидируют payload в runtime: ApiDto должен пройти guard/decoder перед превращением в domain model.
  5. Проверяю через DTO/type boundary table: raw field, interface/type choice, domain shape, runtime check, UI fallback.

Мини-пример

interface User { id: string }
type Status = 'idle' | 'loading' | 'done';

Углубление (2-3 минуты)

  1. Production-сценарий: backend добавляет status: 'archived', а UI union знал только 'active' | 'blocked'; без boundary handling экран падает или показывает неверный state.
  2. Edge-case: declaration merging у interface может быть полезен для расширения внешних типов, но опасен, если случайно меняет global shape.
  3. Edge-case: union вида type Status = 'idle' | 'loading' | 'error' нельзя удобно выразить interface.
  4. Практика: выполните TypeScript boundary pack для UserApiDto -> User -> UserViewState.
  5. Readiness: вы можете объяснить выбор type/interface и отдельно сказать, где нужна runtime validation.

Типичные ошибки

  1. Делают спор о синтаксисе вместо обсуждения boundary, runtime validation и styleguide.
  2. Используют any на API response и теряют пользу типов дальше по цепочке.
  3. Считают, что interface UserApiDto гарантирует форму JSON после fetch.
  4. Смешивают raw DTO и domain model в одном типе.

Follow-up вопросы

  1. Когда выбрать interface, а когда type?
  2. Почему TypeScript не проверяет JSON после сети?
  3. Чем ApiDto отличается от domain model?
  4. Где declaration merging полезен, а где опасен?

Что повторить

  1. TypeScript lesson 1: object shapes, aliases and unions.
  2. TypeScript boundary pack: DTO/domain/UI mapping.
  3. Free-practice: boundary choice table for ApiDto, DomainModel, ViewState.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

14. Как использовать generics в TypeScript на практике?

Теги: typescript, generics, api-client, type-safety Сложность: Middle

Короткий ответ

Generics позволяют писать переиспользуемые функции/типы с сохранением строгой типизации.

Что сказать на интервью (30-60 секунд)

  1. Generic нужен, когда функция или тип должны сохранить связь между input и output без потери конкретного типа.
  2. Хороший generic ограничивает неизвестное через constraints: T extends { id: string }, а не превращает всё в any.
  3. fetchJson<T>() удобен как compile-time helper, но сам по себе не доказывает, что сервер реально вернул T.
  4. Production-риск: слишком широкий generic создает иллюзию безопасности и протаскивает invalid payload в UI.
  5. Проверяю через generic helper constraints table: helper, T, constraint, unsafe case, runtime guard.

Мини-пример

function first<T>(arr: T[]): T | undefined {
return arr[0];
}

Углубление (2-3 минуты)

  1. Production-сценарий: table component принимает rows: T[] и getRowId: (row: T) => string, сохраняя тип row в render callback.
  2. Edge-case: function parse<T>(json): T без validation просто утверждает тип и может скрыть ошибку API.
  3. Edge-case: если helper требует T extends { id: string }, массив без id не скомпилируется.
  4. Практика: опишите fetchJson<T>(), selectById<T extends { id: string }>(), Table<T>() и укажите, где compile-time заканчивается.
  5. Readiness: вы можете объяснить разницу между inference, explicit generic parameter, constraint and unsafe assertion.

Типичные ошибки

  1. Используют <T> как замену any, хотя связь input/output не нужна.
  2. Пишут fetchJson<User>() и считают, что это runtime validation.
  3. Не ставят constraints и потом обращаются к полям через casts.
  4. Делают generic API таким абстрактным, что его сложнее читать, чем конкретный тип.

Follow-up вопросы

  1. Когда generic лучше overload или конкретного типа?
  2. Что дает T extends { id: string }?
  3. Почему fetchJson<T>() не проверяет runtime shape?
  4. Какой unsafe case сломает слишком широкий generic?

Что повторить

  1. TypeScript lesson 2: generics and constraints.
  2. TypeScript boundary pack: fetchJson<T>() boundary.
  3. Free-practice: generic helper constraints table for API, table and selector helpers.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

15. Что такое discriminated unions и зачем они нужны?

Теги: typescript, discriminated-union, state-modeling, reducers Сложность: Middle

Короткий ответ

Это union объектов с общим дискриминатором (например, type), который позволяет TypeScript безопасно сужать тип.

Что сказать на интервью (30-60 секунд)

  1. Discriminated union моделирует взаимоисключающие состояния через общий field: status, type, kind.
  2. Он убирает impossible states: например, data есть только при success, а error только при error.
  3. TypeScript сужает тип по discriminator, поэтому в каждом branch доступны только корректные поля.
  4. Production-риск: boolean flags вроде isLoading, isError, data могут дать противоречивую комбинацию.
  5. Проверяю через async UI state union и exhaustive check: idle, loading, success, error.

Мини-пример

type State =
| { type: 'idle' }
| { type: 'loading' }
| { type: 'success'; data: User[] }
| { type: 'error'; message: string };

Углубление (2-3 минуты)

  1. Production-сценарий: экран заказа не должен одновременно показывать loader, старые данные и error banner. Union заставляет выбрать один state.
  2. Edge-case: при добавлении нового status: 'empty' exhaustive check в switch должен подсветить все места, где branch не обработан.
  3. Edge-case: discriminator должен быть literal type, а не свободная строка string.
  4. Практика: сделайте async UI state union для idle/loading/success/error/empty и mapping from API result to UI state.
  5. Readiness: вы можете показать, какую impossible комбинацию union запрещает, и где нужен runtime guard для API result.

Типичные ошибки

  1. Используют несколько boolean flags вместо одного union state.
  2. Делают discriminator типом string, из-за чего narrowing теряет силу.
  3. Не добавляют exhaustive check и пропускают новый variant.
  4. Моделируют raw API response как UI state без mapping layer.

Follow-up вопросы

  1. Какую impossible state комбинацию запрещает union?
  2. Что произойдет, если добавить новый variant и не обновить switch?
  3. Почему discriminator должен быть literal?
  4. Где нужна runtime validation, если union описывает API payload?

Что повторить

  1. TypeScript lesson 3: narrowing and discriminated unions.
  2. TypeScript boundary pack: async UI state union.
  3. Free-practice: replace isLoading/isError/data flags with a discriminated union and exhaustive switch.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

16. Какие utility types вы используете чаще всего?

Теги: typescript, utility-types, api, maintainability Сложность: Middle

Короткий ответ

Чаще всего Partial, Pick, Omit, Record, Readonly, ReturnType.

Что сказать на интервью (30-60 секунд)

  1. Utility types помогают получать производные типы из базовой модели без ручного дублирования полей.
  2. Pick и Omit подходят для view models и DTO slices; Partial - для patch/update payload; Readonly - для защиты от мутации; Record - для словарей с известными ключами.
  3. ReturnType и Parameters полезны, когда тип должен следовать за функцией, а не жить отдельно и устаревать.
  4. Production-риск: Partial<User> может случайно разрешить слишком широкий patch, а Pick от domain model может протащить поля, которых нет в API.
  5. Проверяю через utility migration table: source type, target use, utility, forbidden fields, runtime boundary.

Мини-пример

type UserPreview = Pick<User, 'id' | 'name'>;
type UserPatch = Partial<User>;

Углубление (2-3 минуты)

  1. Production-сценарий: форма редактирования пользователя отправляет только изменяемые поля. Pick<User, 'name' | 'email'> безопаснее, чем Partial<User>, если role нельзя менять из этой формы.
  2. Edge-case: Record<string, User> не гарантирует, что ключи реально существуют; для фиксированных ключей лучше union ключей: Record<Role, Permission[]>.
  3. Edge-case: Readonly<T> защищает compile-time присваивание, но не делает runtime object immutable.
  4. Практика: заполните utility type migration table для User, UserApiDto, UserPatch, UserPreview, RolePermissions.
  5. Readiness: вы можете объяснить не список utility types, а почему конкретный utility уменьшает дублирование и где он опасен.

Типичные ошибки

  1. Используют Partial для любого update и случайно разрешают менять системные поля.
  2. Дублируют типы вручную, а потом забывают синхронизировать изменения.
  3. Берут Pick от domain model для API payload без учета runtime shape.
  4. Думают, что utility type валидирует данные после сети.

Follow-up вопросы

  1. Когда Pick безопаснее, чем Partial?
  2. Почему Readonly<T> не заменяет runtime immutability?
  3. Чем Record<Role, Permission[]> лучше Record<string, Permission[]>?
  4. Где utility type заканчивается и начинается runtime validation?

Что повторить

  1. TypeScript lesson 4: utility types and maintainability.
  2. TypeScript boundary pack: DTO/domain/UI mapping.
  3. Free-practice: utility type migration table for one form and one API patch.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

17. Promise.all vs Promise.allSettled?

Теги: javascript, promise-all, allsettled, resilience Сложность: Middle

Короткий ответ

Promise.all падает на первой ошибке, Promise.allSettled дожидается завершения всех промисов и возвращает статус каждого.

Что сказать на интервью (30-60 секунд)

  1. Promise.all подходит, когда результат имеет смысл только если успешны все операции; при первом reject общий promise тоже reject.
  2. Promise.allSettled подходит, когда нужны частичные результаты и статус каждой операции: например, несколько независимых виджетов.
  3. Promise.all не отменяет остальные promise автоматически; они продолжают выполняться, если их явно не abort/cancel.
  4. Production-риск: одна необязательная ошибка может завалить весь экран, либо наоборот partial failure может быть скрыт как успех.
  5. Проверяю через promise aggregation matrix: requests, must all succeed?, partial UI?, failure policy, timeout/cancel.

Мини-пример

const results = await Promise.allSettled([p1, p2, p3]);

Углубление (2-3 минуты)

  1. Production-сценарий: checkout должен получить price, stock и payment config. Если payment config не загрузился, продолжать нельзя - это Promise.all.
  2. Production-сценарий: dashboard грузит weather, news, tasks. Один виджет может показать error, остальные остаются полезными - это Promise.allSettled.
  3. Edge-case: при Promise.all([fastReject, slowFetch]) slow fetch не отменяется сам, поэтому для network нужно добавить AbortController.
  4. Практика: заполните aggregation matrix для checkout, dashboard widgets, profile page, bulk upload.
  5. Readiness: вы можете объяснить failure policy и partial state, а не только "all падает, allSettled ждет".

Типичные ошибки

  1. Используют Promise.all для независимых виджетов и получают пустой экран из-за одной ошибки.
  2. Используют allSettled, но не показывают пользователю partial failure.
  3. Думают, что Promise.all отменяет остальные запросы.
  4. Не задают timeout/cancel policy для долгих запросов.

Follow-up вопросы

  1. Что произойдет с остальными promise после первого reject в Promise.all?
  2. Когда partial result лучше полного fail?
  3. Как показать пользователю mixed success/mixed failure?
  4. Где добавить timeout или cancellation?

Что повторить

  1. JS lesson 2: promise, async order and error handling.
  2. Interview pattern taxonomy: execution-order.
  3. Free-practice: promise aggregation matrix for four product scenarios.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

18. Как работает AbortController и зачем он нужен?

Теги: javascript, abortcontroller, cancellation, requests Сложность: Middle

Короткий ответ

AbortController позволяет отменять fetch и другие abortable операции, чтобы не держать лишние запросы и состояния.

Что сказать на интервью (30-60 секунд)

  1. AbortController создает signal, который передают в abortable API, чаще всего fetch.
  2. controller.abort() переводит signal в aborted state, а fetch обычно завершается reject с AbortError.
  3. Это нужно для cleanup в useEffect, отмены устаревших search requests, timeout и ухода со страницы.
  4. Abort не откатывает уже выполненное действие на сервере и работает только там, где API поддерживает signal.
  5. Проверяю через cancellation reproduction: быстрый ввод a -> ab -> abc, abort старых запросов, запись только актуального результата.

Мини-пример

const c = new AbortController();
fetch('/api', { signal: c.signal });
c.abort();

Углубление (2-3 минуты)

  1. Production-сценарий: autocomplete отправляет запрос на каждую смену query. Старый медленный ответ не должен перетереть новый.
  2. Edge-case: abort после того, как сервер уже обработал запрос, не отменяет side effect на backend.
  3. Edge-case: не все async операции abortable; для них нужен request id, ignore flag или собственная cancellation logic.
  4. Практика: выполните React effect/state debug pack и отдельно запишите abort, ignore stale, timeout.
  5. Readiness: вы можете отличить network cancellation от защиты state update на клиенте.

Типичные ошибки

  1. Создают AbortController, но не передают signal в fetch.
  2. Не отличают abort error от настоящей ошибки сервера.
  3. Считают, что abort отменяет действие, которое сервер уже выполнил.
  4. Не делают cleanup в effect и получают stale state update.

Follow-up вопросы

  1. Что именно происходит при controller.abort()?
  2. Как отличить AbortError от server error?
  3. Почему abort не всегда заменяет request id?
  4. Где поставить cleanup в React effect?

Что повторить

  1. React lesson 2: effect cleanup and cancellation.
  2. React effect/state debug pack: stale response reproduction.
  3. Free-practice: cancellation reproduction for autocomplete with timeout.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

19. Что такое делегирование событий и когда оно полезно?

Теги: javascript, event-delegation, dom, performance Сложность: Middle

Короткий ответ

Это обработка событий на общем родителе вместо подписки на каждый дочерний элемент.

Что сказать на интервью (30-60 секунд)

  1. Делегирование использует bubbling: один listener на родителе обрабатывает события от дочерних элементов.
  2. Это полезно для больших или динамических списков, где элементы добавляются/удаляются без пересоздания listener на каждый item.
  3. Механика обычно строится на event.target, closest, dataset и проверке, что найденный элемент принадлежит нужному контейнеру.
  4. Production-риск: не все события bubbling-friendly, вложенные элементы могут дать неверный target, а listener может поймать чужой nested markup.
  5. Проверяю через delegated event checklist: event bubbles?, selector, container guard, dynamic item, keyboard/a11y path.

Мини-пример

list.addEventListener('click', e => {
const btn = e.target.closest('[data-id]');
if (!btn) return;
});

Углубление (2-3 минуты)

  1. Production-сценарий: таблица с 5 000 строк имеет кнопки action. Один listener на tbody проще и дешевле, чем listener на каждую кнопку.
  2. Edge-case: клик по иконке внутри кнопки дает target на SVG, поэтому нужен closest('[data-id]').
  3. Edge-case: focus не bubble так же, как click; нужно знать focusin/focusout или другой подход.
  4. Практика: заполните delegated event checklist для todo list, table actions, menu, nested card.
  5. Readiness: вы можете объяснить target/currentTarget, bubbling и guard against wrong container.

Типичные ошибки

  1. Используют event.target без closest и ломают клики по вложенным элементам.
  2. Не проверяют, что найденный элемент принадлежит текущему контейнеру.
  3. Делегируют события, которые не всплывают ожидаемым образом.
  4. Забывают keyboard path и делают interaction только mouse-only.

Follow-up вопросы

  1. Чем event.target отличается от event.currentTarget?
  2. Зачем нужен closest?
  3. Какие события плохо подходят для простого delegation?
  4. Как проверить keyboard/a11y поведение delegated action?

Что повторить

  1. JS lesson 2: event loop and DOM events.
  2. Мосты практики: Browser storage and CORS плюс DOM/a11y audit pack.
  3. Free-practice: delegated event checklist for a dynamic list with nested buttons.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

20. Что такое tree shaking и от чего он зависит?

Теги: frontend, tree-shaking, bundling, performance Сложность: Middle

Короткий ответ

Tree shaking удаляет неиспользуемые экспорты из бандла, если код и сборка позволяют это статически определить.

Что сказать на интервью (30-60 секунд)

  1. Tree shaking удаляет неиспользуемые exports, когда bundler может статически понять import/export graph.
  2. Лучше всего работает с ESM (import/export), production build, minifier и package metadata вроде sideEffects.
  3. Хуже работает с dynamic require, CommonJS, namespace imports с side effects, barrel files с побочными эффектами и кодом, который выполняется при import.
  4. Production-риск: разработчик думает, что импортировал одну функцию, а в bundle попал весь package или side-effect module.
  5. Проверяю через tree-shaking import audit: import, module format, side effects, bundle evidence, safer import.

Мини-пример

import { usedFn } from './utils.js'; // не импортируем весь namespace без нужды

Углубление (2-3 минуты)

  1. Production-сценарий: импорт import _ from 'lodash' может принести лишний код, а точечный ESM import или lightweight helper уменьшает bundle.
  2. Edge-case: export * from './moduleWithSideEffect' в barrel file может помешать удалению кода, если module выполняет side effect.
  3. Edge-case: tree shaking не обязан удалить код, если package помечен как having side effects или bundler не может доказать безопасность удаления.
  4. Практика: заполните import audit для lodash, icon library, date library, internal barrel file, CSS side-effect import.
  5. Readiness: вы можете назвать условия, при которых unused export будет удален, и условия, когда он останется в bundle.

Типичные ошибки

  1. Думают, что любой named import автоматически гарантирует маленький bundle.
  2. Не проверяют production build и bundle analyzer/evidence.
  3. Делают barrel exports с побочными эффектами.
  4. Путают tree shaking, code splitting и minification.

Follow-up вопросы

  1. Почему ESM помогает tree shaking?
  2. Чем tree shaking отличается от code splitting?
  3. Как sideEffects влияет на удаление модулей?
  4. Как доказать, что импорт реально уменьшил bundle?

Что повторить

  1. Package Managers and Bundlers: ESM, bundle and dependency risk.
  2. Interview pattern taxonomy: performance-budget.
  3. Free-practice: tree-shaking import audit for five imports.

Связанные модули и карта

  1. Обучение: JavaScript
  2. Обучение: React
  3. Карта подготовки: React

Куда дальше

  1. Вернитесь в модуль: React.
  2. Сверьтесь с Мостами практики: Frontend базовое покрытие и выберите слабую строку.
  3. Продолжайте по маршруту: Middle трек.
  4. Закрепите один ответ через named free-practice из этой страницы или в Песочнице.