Срез 3
Изучение теории. Неделя 3
1. JS. Inheritance
1.1. Что такое прототипное наследование в JavaScript и как оно связано с цепочкой прототипов при поиске свойства у объекта?
Прототипное наследование — это способ передачи функционала между объектами через специальную ссылку [[Prototype]].
- При обращении к свойству объекта, движок сначала ищет его в самом объекте.
- Если свойство не найдено, поиск переходит к его прототипу, затем к прототипу прототипа и так далее — это и называется цепочкой прототипов.
- Поиск продолжается до тех пор, пока свойство не будет найдено или цепочка не закончится на
null.
1.2. Объясните различие между свойствами proto и prototype: где они находятся, для чего используются и как влияют на наследование
__proto__есть у каждого объекта. Это фактическая ссылка, по которой объект ищет свойства у своего родителя в цепочке наследования.prototypeесть только у функций-конструкторов. Это шаблон, который функция выдает каждому новому объекту (в его__proto__) при создании черезnew.
Связь: Если вызвать const obj = new F(), то obj.__proto__ становится равен F.prototype.
1.3. Как бы вы устно описали подход к написанию полифила метода, который должен быть доступен всем массивам: куда его добавлять (прототип или как статический метод) и какие риски/ограничения у каждого варианта?
Добавлять нужно в Array.prototype, чтобы метод наследовался всеми экземплярами через цепочку прототипов.
- Array.prototype:
[].myMethod()- Риск: Конфликт имен с будущими стандартами JS и «загрязнение» глобальных объектов.
- Статический метод:
Array.myMethod([])- Ограничение: Недоступен экземплярам напрямую, это не полифил поведения массива, а просто утилита.
2. React. Render
2.1. Что делает ReactDOM.render() и какую роль он играет при отображении JSX на странице?
ReactDOM.render() связывает React и браузер. Он берет дерево React-элементов (JSX) и превращает его в настоящие DOM-узлы внутри указанного HTML-контейнера.
2.2. Как в JSX происходит вставка переменных и выражений, и какие типы значений React может корректно отрендерить без ошибок?
Вставка происходит через фигурные скобки { }. Внутри них можно писать любое валидное JavaScript-выражение.
React рендерит без ошибок:
- Примитивы: Строки и числа (выводятся как текст).
- Массивы: Рендерит каждый элемент списка последовательно.
- JSX-элементы: Другие компоненты или HTML-теги.
- Игнорируемые значения: true, false, null, undefined (не отображаются на экране, но не вызывают ошибок — удобно для условий).
Что вызовет ошибку:
- Объекты: Прямой рендер обычного объекта
2.3. Объясните, как ReactDOM.render() (и механизм согласования React) обновляет уже отрисованные элементы: чем отличается виртуальный DOM от реального DOM и почему это обычно повышает производительность?
Обновление происходит через Virtual DOM — легкую копию реального DOM.
- Менять реальный DOM дорого и медленно. Виртуальный DOM — это просто данные в памяти, их сравнение происходит мгновенно.
- При изменении данных React создает новый Virtual DOM и сравнивает его с предыдущим(Diffing).
- React находит только изменившиеся узлы и применяет к реальному DOM минимально необходимые правки одним пакетом.
3. React. Fragments
3.1. Что такое React Fragment и для чего его обычно используют в JSX?
React Fragment — это специальный компонент, который позволяет группировать список дочерних элементов без создания лишнего узла (например, <div>) в DOM-дереве.
3.2. В каких ситуациях вы бы выбрали React.Fragment вместо оборачивания элементов в <div>, и какие практические последствия это имеет для DOM и стилизации?
Использование React.Fragment предпочтительно для:
- Чистоты DOM: чтобы дерево элементов оставалось легким и в нем не было лишних оберток.
- Сохранения структуры CSS: чтобы лишний тег не ломал сетки Flexbox или Grid.
- Валидности HTML: чтобы не вставлять <div> туда, где по стандарту разрешены только специфичные теги (например, внутрь <table> или <ul>).
3.3. Зачем могут понадобиться ключи (key) при использовании Fragments, например при рендеринге списков, и чем отличается поведение при использовании явного с key от короткого синтаксиса <>...</>?
Ключи в Fragment нужны для идентификации элементов при рендеринге списков, чтобы React понимал, какие части интерфейса нужно обновить, а не перерисовывать полностью.
Главное отличие:
<React.Fragment key={...}>: поддерживает атрибутkey, поэтому обязателен при использовании.map().<>...</>: не поддерживает атрибуты (включаяkey), поэтому подходит только для разовой группировки элементов.
4. React. Fiber
4.1. Что такое Fiber в React и какую проблему в процессе reconciliation он помогает решать?
Fiber — это новый движок рендеринга React, который превращает процесс обновления интерфейса из непрерывного (стекового) в прерывистый.
Главная проблема, которую он решает — блокировка основного потока (main thread) долгими вычислениями. Благодаря Fiber, React может разделять работу на части, ставить её на паузу для обработки анимаций или ввода пользователя и возвращаться к ней позже, обеспечивая плавность интерфейса.
4.2. Опишите на высоком уровне, как Fiber организует работу рендера: что значит «разбить работу на части» и как это влияет на отзывчивость интерфейса?
Fiber организует работу как очередь мелких задач, превращая рекурсивный обход дерева в связный список.
- Каждое обновление делится на «файберы» (атомарные юниты работы). React выполняет их по очереди, делая микро-паузы после каждого юнита.
- В этих паузах React проверяет наличие высокоприоритетных событий (клик, ввод текста). Если они есть, он прерывает текущий рендер, обрабатывает действие пользователя и только потом возвращается к отрисовке.
4.3. Объясните различие между фазами render (reconciliation) и commit в React с точки зрения Fiber: какие операции могут быть прерваны/возобновлены, а какие должны выполняться атомарно, и почему?
Различие между фазами заключается в прерываемости и работе с DOM:
- Фаза Render (Reconciliation): Это построение дерева Fiber и вычисление разницы (diffing) «в черновике». Она прерываема, может ставиться на паузу и выполняться асинхронно, так как не производит видимых изменений.
- Фаза Commit: Это физическое применение вычисленных изменений в реальный DOM. Она атомарна (непрерываема), чтобы пользователь не увидел промежуточное, «разорванное» состояние интерфейса.
5. React. VirtualDOM
5.1. Что такое Virtual DOM в React и зачем он нужен при обновлении интерфейса?
Virtual DOM — это легкая копия реального DOM в виде JavaScript-объектов.
Он нужен для минимизации тяжелых операций с браузерным DOM. Вместо того чтобы перерисовывать страницу при каждом мелком изменении, React сначала просчитывает изменения в памяти, а затем применяет только необходимые «патчи» к реальному интерфейсу.
5.2. Объясните, как React использует Reconciliation для сравнения предыдущего и нового дерева элементов и принятия решения, что обновлять в реальном DOM
При обновлении состояния React создает новое дерево Virtual DOM и сравнивает его с предыдущим (Diffing algorithm).
- Сравнение типов: Если тип элемента изменился (например, <div> заменили на ), React уничтожает старое дерево и строит новое с нуля.
- Сравнение атрибутов: Если тип тот же, React обновляет только изменившиеся атрибуты (например, className или style).
- Рекурсия по детям: React одновременно обходит списки дочерних элементов обоих деревьев, чтобы найти отличия.
5.3. Какие факторы могут ухудшать производительность при Reconciliation (например, частые перерисовки, нестабильные ключи, изменение структуры дерева), и как вы бы устно объяснили, почему это происходит на уровне сравнения Virtual DOM?
- Нестабильные ключи (key)
- Почему: Если использовать index или Math.random(), React теряет связь между старым и новым элементом. При простом сдвиге списка он решит, что все элементы изменились, и перерисует их заново вместо простой перестановки.
- Изменение структуры дерева
- Почему: Если вы обернули компонент в новый <div>, алгоритм увидит смену типа на верхнем уровне, удалит всю ветку и создаст её заново, даже если внутри ничего не поменялось.
- Частые перерисовки (Re-renders)
- Почему: Каждое обновление запускает цикл сверки. Даже если итоговый DOM не меняется, процессор тратит время на создание объектов Virtual DOM и их полное сравнение (Diffing).
5.4. Объясните, как Fiber позволяет прерывать и возобновлять работу (time slicing) и как при этом обеспечивается согласованность дерева: какую роль играют приоритеты/lanes и двойная буферизация (current vs workInProgress)
Fiber — позволяет прерывать/возобновлять рендеринг.
- Time slicing: работа разбита на части, можно прервать для важных задач
- Двойная буферизация: current (показывается) и workInProgress (строится)
- Lanes: приоритеты определяют очередность выполнения
6. React. Functional components
6.1. Что такое функциональный компонент в React и какую задачу он решает в приложении?
Что это: Обычная JavaScript-функция, принимающая props и возвращающая JSX.
Задача: Описывать интерфейс как чистую декларативную проекцию данных без громоздкого синтаксиса классов.
6.2. Как в функциональном компоненте управлять состоянием и чем отличается подход со state в функциональных компонентах от классовых?
Как: Через хук useState.
Отличия:
- Классы: Состояние — это один объект this.state, обновляется через setState (слияние).
- Функции: Состояние может быть атомарным (несколько useState), обновляется функцией-сеттером (замена). Нет this, данные сохраняются между рендерами за счет замыканий React.
6.3. Какие типичные причины лишних перерисовок у функциональных компонентов и какие устные стратегии оптимизации производительности вы бы применили (например, мемоизация, стабильность ссылок, разделение компонентов)?
Причины:
- Обновление родителя (рендер идет вниз по дереву).
- Нестабильные ссылки: Создание новых объектов/функций внутри тела компонента при каждом рендере.
Стратегии оптимизации:
- React.memo: Пропускает рендер, если props не изменились.
- useCallback / useMemo: Сохраняют стабильную ссылку на функции и объекты, чтобы не «ломать» React.memo у дочерних компонентов.
- Разделение (Composition): Вынос часто меняющегося состояния в отдельный мелкий компонент, чтобы не перерисовывать тяжелую ветку дерева.
7. React. Class components
7.1. Что такое классовый компонент в React и какие обязательные элементы у него есть (например, render, props)?
Что это: ES6-класс, наследуемый от React.Component.
Обязательные элементы:
- Метод render() — возвращает JSX.
- props — объект входных данных (доступен через this.props).
7.2. Как в классовом компоненте работает состояние: чем отличается this.state от props и как правильно инициировать и обновлять состояние, чтобы не нарушать принципы React?
- Props: Неизменяемы (read-only), приходят извне.
- State: Внутренние данные, изменяемы.
- Инициализация: В constructor через this.state = { … }.
- Обновление: Только через this.setState(). Прямая мутация (this.state.x = 1) запрещена, так как не вызывает рендер.
7.3. Объясните, как в классовых компонентах реализуется обработка ошибок через Error Boundaries: какие методы жизненного цикла участвуют, что именно они перехватывают и какие ограничения у такого подхода?
Как работают: Компоненты-классы, ловящие ошибки в дочернем дереве.
Методы:
- static getDerivedStateFromError(error) — обновляет state, чтобы показать запасной UI.
- componentDidCatch(error, info) — для логирования ошибки.
Что перехватывают: Ошибки при рендеринге, в методах жизненного цикла и конструкторах детей.
Ограничения: Не ловят ошибки в обработчиках событий, асинхронном коде (setTimeout), самом себе и при серверном рендеринге.
8. React. Props
8.1. Что такое props в React и для чего они используются при передаче данных от родительского компонента к дочернему?
Props (свойства) — это объект с данными, который передается от родительского компонента дочернему. Они используются для конфигурации потомка и передачи ему информации. Внутри дочернего компонента props доступны только для чтения (immutable).
8.2. В чём ключевое различие между props и state, и как это различие влияет на то, кто и когда может изменять данные в компоненте?
Props — это внешние данные, которыми управляет родитель; компонент-получатель не может их изменять.
State — это внутренние данные, которые компонент создает и меняет сам (useState).
Различие определяет логику обновлений: компонент сам решает, когда обновить свой state, но он полностью зависит от родителя в вопросе получения новых props. Изменение любого из них приводит к перерисовке.
8.3. Как организовать «обратный поток данных» от дочернего компонента к родительскому с помощью props, и какие типичные ошибки или ограничения при этом встречаются?
Организуется через callback-функции: родитель передает функцию через props, а дочерний компонент вызывает её, передавая данные в аргументах.
Ошибки и ограничения:
- Prop Drilling: передача функции через цепочку компонентов, которым она не нужна.
- Мутация: попытка изменить проп-функцию или объект напрямую.
- Сложность отслеживания: в глубоких деревьях трудно понять, где именно зародилось событие изменения.
9. React. State
9.1. Что такое состояние (state) в React и чем оно отличается от props с точки зрения того, кто и как может его изменять?
State — это внутренние данные компонента, которыми он управляет сам.
- Кто меняет: Только сам компонент через useState или setState.
- Отличие: В отличие от props, которые приходят извне и неизменны для получателя, state инкапсулирован и может обновляться в ответ на действия пользователя или события.
9.2. Почему обновления состояния через setState могут быть асинхронными и как это влияет на ситуацию, когда вы делаете несколько обновлений состояния подряд?
Обновления асинхронны для оптимизации производительности: React ждет завершения всех обработчиков событий перед перерисовкой.
- Проблема: При нескольких обновлениях подряд (setCount(count + 1)) последующие могут использовать старое значение переменной.
- Решение: Использовать функцию-обработчик: setCount(prev => prev + 1).
9.3. Объясните, как пакетирование (batching) обновлений состояния влияет на количество перерисовок и согласованность данных, и какие подходы вы бы выбрали, чтобы минимизировать лишние рендеры в большом приложении со сложным состоянием
Batching (пакетирование) объединяет несколько изменений состояния в одну перерисовку (render), что предотвращает «мерцание» интерфейса и экономит ресурсы.
Как минимизировать лишние рендеры:
- React.memo: для предотвращения ререндера дочерних компонентов при неизменных пропсах.
- useMemo / useCallback: для мемоизации тяжелых вычислений и ссылок на функции.
- Локализация состояния: спуск state как можно ниже по дереву компонентов.
- Zustand / Redux: для точечного извлечения данных без проброса через всё дерево.
10. React. Lifecycle methods
10.1. Какие основные этапы жизненного цикла у классового компонента React (монтирование, обновление, размонтирование) и какие методы обычно к ним относятся?
- Монтирование (Mounting): создание и вставка в DOM.
- Методы: constructor, static getDerivedStateFromProps, render, componentDidMount.
- Обновление (Updating): изменение props или state.
- Методы: static getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate.
- Размонтирование (Unmounting): удаление из DOM.
- Метод: componentWillUnmount.
10.2. В каких случаях срабатывает componentDidUpdate, какие аргументы он получает и как их использовать, чтобы корректно реагировать на изменения props или state и не попасть в бесконечный цикл обновлений?
Срабатывает сразу после обновления (кроме первого рендера).
- Аргументы: prevProps, prevState, snapshot (из getSnapshotBeforeUpdate).
- Как использовать: Сравнивать текущие значения с предыдущими.
- Бесконечный цикл: Чтобы его избежать, любой вызов this.setState обязан находиться внутри условного оператора, проверяющего изменение конкретных данных (например, if (this.props.id !== prevProps.id)).
10.3. Объясните назначение getSnapshotBeforeUpdate: когда он вызывается, что должен возвращать, как его результат попадает в componentDidUpdate и в каких практических сценариях это помогает (например, сохранение позиции прокрутки) без ухудшения производительности?
Вызывается в фазе «пред-коммита», когда изменения уже вычислены, но DOM еще не обновлен.
- Возвращает: Любое значение (snapshot) или null.
- Передача данных: Результат попадает в componentDidUpdate как третий аргумент.
- Сценарий: Сохранение состояния DOM, которое изменится после рендера (например, позиция скролла в чате). Это позволяет в componentDidUpdate программно скорректировать прокрутку так, чтобы пользователь не потерял место при добавлении новых сообщений.
10.4. Зачем нужен shouldComponentUpdate, какие данные он получает и как его решение может повлиять на производительность и поведение компонента?
shouldComponentUpdate — жизненный цикл для оптимизации производительности.
Что получает:
- nextProps — новые пропсы
- nextState — новый стейт
Как влияет:
- Возвращает true → компонент перерисуется
- Возвращает false → рендер и componentDidUpdate пропустятся
Зачем: предотвращает лишние рендеры, когда данные не изменились, ускоряя работу приложения.
11. React. Why hooks?
11.1. Почему в React появились хуки и какие проблемы классовых компонентов они помогают решить?
Хуки решают три ключевые проблемы классов:
- Переиспользование логики: позволяют выделять состояние в кастомные хуки без «ада дескрипторов» (HOC, Render Props).
- Раздутые компоненты: позволяют группировать код по смыслу (например, подписка на событие), а не разносить его по методам жизненного цикла (componentDidMount / componentWillUnmount).
- Сложность this: избавляют от путаницы с контекстом вызова и привязкой методов.
11.2. Объясните правила использования хуков (например, почему их нельзя вызывать в условиях или циклах) и к каким ошибкам это приводит
- Только на верхнем уровне: нельзя вызывать внутри циклов, условий или вложенных функций.
- Только в React-функциях: в функциональных компонентах или кастомных хуках.
Причина: React полагается на строгий порядок вызова хуков для связи состояния с конкретной переменной. Последствия: Нарушение порядка (из-за условия или цикла) приведет к тому, что React вернет данные не того хука, что вызовет баги или падение приложения с ошибкой “Rendered more/fewer hooks than during the previous render”.
11.3. В каких ситуациях useMemo и useCallback действительно улучшают производительность, а в каких могут навредить? Объясните, как вы бы это оценивали на практике
- Когда полезны: Тяжелые вычисления (фильтрация больших массивов) или передача ссылок в компоненты, обернутые в React.memo, чтобы избежать их лишней перерисовки.
- Когда вредны: Для простых операций (сложение чисел, создание обычных функций). Затраты на сравнение зависимостей и выделение памяти под кэш могут превысить выгоду от «оптимизации».
- Оценка: Использовать React Profiler для замера времени рендера. Если время выполнения компонента невелико, а React.memo не используется, хуки лучше не внедрять.
12. React. useState/useReducer/useContext
12.1. Что такое хук useState в React и в каких случаях его обычно используют для управления состоянием компонента?
useState — это базовый хук React, который позволяет добавлять состояние в функциональные компоненты.
- Когда использовать: Для хранения простых значений (примитивов), небольших объектов или независимых переменных, которые не требуют сложной логики пересчета при изменении.
12.2. Объясните, в чём ключевые различия между useState и useReducer и по каким признакам вы бы выбрали один из них для конкретной задачи
Ключевое различие — в масштабе и связности данных.
- useState: Идеален для простых данных. Логика обновления находится прямо в обработчиках событий. Выбирайте его, если состояние состоит из 1–2 независимых полей.
- useReducer: Подходит для сложного состояния (объекты с глубокой вложенностью) или когда следующее состояние зависит от предыдущего. Логика вынесена в отдельную функцию-редюсер. Выбирайте его, если нужно централизовать управление данными или если изменение одного поля должно влиять на остальные.
12.3. Как useContext может повлиять на производительность приложения: почему обновления контекста могут вызывать лишние перерисовки и какие стратегии вы бы предложили для их уменьшения?
Причина перерисовок: Когда значение (value) в Provider обновляется, React автоматически ререндерит все компоненты-потребители (через useContext), независимо от того, какую именно часть данных из контекста они используют.
Стратегии оптимизации:
- Дробление (Splitting): Разделение одного большого контекста на несколько маленьких по зонам ответственности.
- Мемоизация (useMemo): Передача в value объекта, обернутого в useMemo, чтобы ссылка на него не менялась при каждом рендере провайдера.
- Разделение на “Данные” и “Действия”: Создание двух контекстов — один для стейта, другой для функций обновления (которые не меняются).
- Использование React.memo: Обертывание дочерних компонентов, чтобы изменения в родителе не вызывали их автоматический ререндер, если их пропсы стабильны.
13. React. useEffect/useLayoutEffect
13.1. Что делает хук useEffect в функциональном компоненте React и в какие моменты он обычно выполняется?
useEffect предназначен для выполнения побочных эффектов (side effects): запросов к API, подписок или манипуляций с DOM.
Когда выполняется: После того как компонент был отрисован на экране (после завершения цикла рендеринга и этапа “paint”).
13.2. Объясните ключевые различия между useEffect и useLayoutEffect: когда они запускаются относительно отрисовки и покраски (paint) и как это влияет на пользовательский интерфейс?
Ключевое различие — в моменте запуска относительно отрисовки браузером.
- useEffect: Запускается асинхронно после того, как браузер отрисовал изменения на экране. Не блокирует отрисовку.
- useLayoutEffect: Запускается синхронно после того, как React применил изменения к DOM, но до того, как браузер успел их отрисовать (этап “paint”).
13.3. Представьте, что после рендера вам нужно измерить размеры DOM-элемента и сразу скорректировать раскладку, чтобы избежать «прыжка» интерфейса. Как вы решите задачу и почему выберете useLayoutEffect или useEffect? Какие риски для производительности и блокировки рендера при этом нужно учитывать?
Для этой задачи следует использовать useLayoutEffect.
- Почему: Поскольку он выполняется до “покраски”, любые изменения состояния или DOM, сделанные внутри него, применятся в том же кадре. Это позволяет избежать визуального “скачка” (фликкера), который возник бы при использовании useEffect.
- Риски: useLayoutEffect является блокирующим. Пока код внутри него не выполнится, браузер не сможет отрисовать интерфейс. Тяжелые вычисления внутри этого хука приведут к заметным задержкам и снижению производительности (падению FPS).
14. React. Custom hooks
14.1. Что такое пользовательский хук в React и какую проблему он решает по сравнению с копированием логики между компонентами?
Пользовательский хук — это обычная JavaScript-функция, название которой начинается с use, и которая может вызывать другие хуки.
Решаемая проблема: Позволяет извлекать и переиспользовать логику состояния (stateful logic) между компонентами без изменения их иерархии. Избавляет от дублирования кода и делает компоненты чище.
14.2. Какие «Правила хуков» нужно соблюдать при использовании пользовательских хуков, и почему нарушение этих правил приводит к ошибкам в работе React?
- Только на верхнем уровне: Нельзя вызывать хуки внутри условий, циклов или вложенных функций.
- Только в функциях React: Вызов допустим только в функциональных компонентах или других пользовательских хуках.
Почему это важно: React полагается на строгий порядок вызовов хуков при каждом рендере, чтобы правильно сопоставлять внутреннее состояние с конкретным вызовом. Нарушение порядка (например, из-за условия if) приведет к тому, что React вернет данные не того хука, что вызовет баги или падение приложения.
14.3. Как бы вы спроектировали сложный пользовательский хук, который объединяет несколько хуков и обращается к внешнему API: какие ответственности вы бы разделили (состояние, эффекты, отмена запросов, обработка ошибок, кеширование), и как обеспечить переиспользуемость и предсказуемость поведения?
Для обеспечения предсказуемости и чистоты следует разделить ответственности:
- Состояние (State): Использовать useReducer для управления сложным статусом (idle, loading, success, error) и данными.
- Эффекты (Effects): useEffect для запуска запроса при изменении параметров (URL, ID).
- Отмена запросов: Обязательное использование AbortController в функции очистки (cleanup) эффекта для предотвращения утечек памяти и race conditions.
- Обработка ошибок: Блок try/catch с записью сообщения об ошибке в стейт для отображения в UI.
- Кеширование: Вынос кеша во внешний объект или использование контекста/библиотек (типа React Query), чтобы избежать лишних сетевых вызовов.
- Интерфейс (API): Возвращать объект с данными, флагами состояния и функцией для ручного перезапуска (refetch).
15. React. Other hooks
15.1. Что такое useRef в React и чем он отличается от useState с точки зрения влияния на рендеры компонента?
useRef — это хук, возвращающий объект с изменяемым свойством .current, которое сохраняется между рендерами.
Главное отличие: В отличие от useState, изменение значения в useRef не вызывает рендер компонента. Он используется для прямого доступа к DOM или хранения переменных, которые не должны влиять на визуальное отображение.
15.2. В каких случаях useLayoutEffect предпочтительнее useEffect, и какие риски или побочные эффекты могут возникнуть при его неправильном использовании?
Предпочтительнее: Когда нужно внести изменения в DOM (измерить размеры, изменить положение) до того, как пользователь увидит интерфейс.
- Преимущество: Предотвращает визуальное мерцание («прыжки» контента).
- Риски: Он блокирует отрисовку браузером. Если код внутри выполняется долго, приложение будет казаться зависшим (снижение FPS). Не подходит для тяжелых вычислений или сетевых запросов.
15.3. Объясните, как и зачем использовать useImperativeHandle вместе с forwardRef: какие проблемы он решает при проектировании публичного API компонента и какие есть альтернативы?
Зачем: Позволяет родительскому компоненту вызывать методы внутри дочернего через ref. forwardRef передает ссылку вниз, а useImperativeHandle ограничивает то, что будет доступно родителю.
Проблемы: Скрывает внутреннюю реализацию и предоставляет только контролируемое API (например, методы .focus() или .scrollIntoView()), предотвращая прямой и бесконтрольный доступ к узлам DOM.
Альтернативы:
- Поднятие состояния (Lifting state up): Управление поведением через пропсы (рекомендуемый путь).
- callback-refs: Передача функции из родителя в дочерний компонент для получения ссылки на элемент.