Вопросы и ответы с собеседований по React.js
Вопросы и ответы по React.js для собеседования фронтенд-разработчика. Что такое виртуальный DOM, в чём разница между useEffect и useLayoutEffect, что такое батчинг ререндеров, конкуретный режим, React Context, Suspense и многое другое.
Что такое React?
React - JavaScript библиотека, разработанная Facebook в 2011. В основе лежит компонентный подход, позволяющий создавать переиспользуемые UI-компоненты. Служит для создания сложных интерактивных интерфейсов для веб-сайтов и мобильных приложений.
Что такое виртуальный DOM?
Виртуальный DOM (VDOM) - это копия Document Object Model, которую в своем ядре выстраивает фреймворк (в данном случае React). Вместо того, чтобы при каждом изменении данных заставлять браузер переформировывать DOM, внутри фреймворка происходит перерасчёт Virtual DOM. Дальше по результатам сравнения построенного Virtual DOM и DOM фреймворк вносит точечные изменения в реальный DOM. Этот механизм существенно повышает производительность React-приложения, так как позволяет избежать ненужных перерисовок и вычислений браузеру.
Что такое JSX?
JSX - это дополнение к синтаксису JS, которое позволяет писать HTML в React компонентах. JSX - синтаксический сахар для функции React.createElement(component, props, ...children). За правильный парсинг и дальнейшую обработку отвечает babel.
Что такое useEffect?
useEffect(() => {
console.log('side effect')
}, [])
useEffect
- это хук, принимающий функцию, которая будет выполнена после каждой перерисовки компонента.
Однако, вторым параметром в useEffect
передается массив зависимостей (переменные состояния) - при его наличии функция, переданная в useEffect
, будет выполняться не при каждой перерисовке, а только в случае изменения элемента из массива зависимостей.
useEffect
вызывается асинхронно после перерисовки компонента.
Что такое useLayoutEffect?
useLayoutEffect(() => {
console.log('side effect')
}, [])
useLayoutEffect
- это аналог useEffect
, отличающийся тем, что выполняется синхронно до перерисовки компонента, что бывает крайне полезным при взаимодействии в функции-коллбеке с DOM.
Как сбросить эффект?
useEffect(() => {
let mounted = true;
setTimeout(() => {
if (mounted) {
setUsername('hello world');
}
}, 4000);
return () => mounted = false;
}, []);
Функция, переданная в useEffect
и useLayoutEffect
, может при необходимости возвращать другую функцию, которая будет вызвана в момент размонтирования компонента, а также перед каждым своим исполнением, чтобы сбросить эффект предыдущего рендера).
Что такое батчинг ререндеров?
Батчингом в React называют процесс группировки нескольких вызовов обновления состояния в один этап ререндера. Это положительно сказывается на производительности.
До React 18 батчинг автоматически работал только для обработчиков DOM событий:
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // Не вызывает ререндер
setFlag(f => !f); // Не вызывает ререндер
// React вызовет ререндер только один раз, в конце
}
/*
function handleClick() {
fetchSomething().then(() => {
// До React 17 следующие вызовы не батчились
// Установка состояния происходит “после” события, в колбэке асинхронного вызова
setCount(c => c + 1); // Спровоцирует ререндер
setFlag(f => !f); // Спровоцирует ререндер
});
}
*/
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
В React 18 все обновления состояния внутри Promise, таймаутов, fetch-запросов будут батчиться также, как для обработчиков DOM-событий.
Код ниже:
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React будет вызывать ререндер только один раз, в конце
}
//работает так же, как и этот код:
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React будет вызывать ререндер только один раз, в конце
}, 1000);
Как отменить батчинг?
Обычно батчинг безопасен и не вызывает проблем при разработке, но если сразу после обновления состояния нужно прочитать изменения в DOM, то можно использовать ReactDOM.flushSync()
для отмены батчинга:
import { flushSync } from 'react-dom'; // Внимание: react-dom, не react
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React вызовет ререндер
flushSync(() => {
setFlag(f => !f);
});
// React вызовет ререндер
}
Что такое React.Suspense?
const App = () => {
return (
<Suspense fallback={<Loading />}>
<SuspendedComponent />
<Sibling />
</Suspense>
);
};
Suspense
предназначен для отображения запасного интерфейса (спиннера) во время ожидания дочерних компонентов. Дочерние компоненты в это время могут выполнять асинхронные вызовы API, либо загружаться через lazy load.
В React 18 фича стала стабильной, получила большие архитектурные изменения под капотом и приобрела название “Конкурентные задержки” (Concurrent Suspense). Смена названия никак не отразится на пользователях.
Что такое SuspenseList?
Предназначен для определения порядка, в котором загружаются и отображаются пользователю напрямую вложенные компоненты Suspense
и SuspenseList
.
<SuspenseList revealOrder="forwards">
<Suspense fallback={'Загрузка...'}>
<ProfilePicture id={1} />
</Suspense>
<Suspense fallback={'Загрузка...'}>
<ProfilePicture id={2} />
</Suspense>
<Suspense fallback={'Загрузка...'}>
<ProfilePicture id={3} />
</Suspense>
...
</SuspenseList>
Бывают случаи, когда в UI необходимо отобразить компоненты в определенном порядке, и если обернуть их в SuspenseList
, то React не отобразит компонент, пока не загрузится предыдущий из списка (этим поведением можно управлять).
Что такое React.StrictMode?
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
StrictMode
— инструмент для обнаружения потенциальных проблем в приложении. Так же как и Fragment
, StrictMode
не рендерит видимого UI. Строгий режим активирует дополнительные проверки и предупреждения для своих потомков.
Примечание: Проверки строгого режима работают только в режиме разработки; они не оказывают никакого эффекта в продакшен-сборке. Строгий режим может быть включён для любой части приложения.
На данный момент StrictMode
помогает в:
- обнаружении небезопасных методов жизненного цикла
- предупреждении об использовании устаревшего API строковых реф
- предупреждении об использовании устаревшего метода findDOMNode
- обнаружении неожиданных побочных эффектов
- обнаружении устаревшего API контекста
- обеспечение переиспользуемости состояния
Что такое конкурентный режим?
Конкурентный режим предназначен для более плавной работы приложения на устройстве пользователя. Одна из областей, где данная фича применяется, это прерываемый рендеринг. Представьте, что пользователь вводит в строку поиска текст. Это событие обновляет состояние компонента, и происходит рендер нового списка результатов. Во время этого процесса залипает ввод: браузер не может обновить введенный в поле текст, так как занимается рендером нового списка результатов. Конкурентный режим исправляет это ограничение, делая рендер прерываемым.
Что такое порталы в React?
Порталы позволяют рендерить дочерние элементы в DOM-узел, находящийся вне DOM-иерархии родительского компонента. Порталы особенно полезны, когда в родительском компоненте заданы стили overflow: hidden или z-index, и нужно, чтобы дочерний элемент визуально выходил за рамки своего контейнера. Это диалоги, модальные окна или всплывающие подсказки.
Что такое React.Fragment?
React.Fragment
- это специальный элемент в React, позволяющий возвращать группу элементов без дополнительного родительского DOM элемента.
Что такое React Context?
Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропсы на промежуточных уровнях.
Что такое refs в React?
Рефы дают возможность получить доступ к DOM-узлам, к которым они привязаны.
Ситуации, в которых использование рефов является оправданным:
- Управление фокусом
- Выделение текста
- Вызов scrollTo
- Вызов анимаций
- Интеграция со сторонними DOM-библиотеками
- Другие случаи
В рефах можно хранить любое значение, а не обязательно ссылку на элемент. Изменение поля ref.current
не приводит к реренденру компонента.
setState синхронный или асинхронный?
setState
- асинхронный, благодаря чему React может оптимизировать процесс изменения состояния и объединить несколько вызовов setState
в один, чтобы отразить все изменения в одной перерисовке вместо нескольких.
В чем разница memo и useMemo?
memo
— это компонент высшего порядка, предоставляемый React.
Он нужен для повышения производительности и подходит для случаев, когда компонент рендерит одинаковый результат при одних и тех же значениях пропсов. В этом случае результат будет мемоизирован. Это значит, что React будет использовать результат последнего рендера, избегая повторного рендеринга.
При использовании memo
пропсы по умолчанию сравниваются поверхностно. Можно передать свою функцию сравнения в качестве второго аргумента (если нужно контролировать сравнение).
useMemo
- это хук, который возвращает мемоизированное значение функции, делающей вычисления. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендере. useMemo
будет повторно вычислять мемоизированное значение только тогда, когда значение какой-либо из зависимостей изменится.
Зачем нужен атрибут key?
Атрибут key
позволяет React понимать, какие именно элементы в списке были модифицированы или удалены, что увеличивает производительность рендеринга. Лучше всего использовать уникальные значения, такие как ID. Индексы использовать не рекомендуется.
Что такое prop drilling?
Prop drilling - это передача свойств напрямую от родителя к ребенку через длинную иерархию компонентов. Избежать этого можно за счет использования Context
или менеджера состояния, такого как Redux.
Как оптимизировать React-приложение?
- избавиться от причин лишних ререндеров
- вынести тяжелые вычисления в отдельный поток (Web Workers)
- использовать useMemo
, useCallback
, React.memo для мемоизации вычислений
- избавиться от утечек памяти
- использовать атрибут key
в списках
- кэшировать негорячие вычисления
- кэшировать ответы запросов сервера на N
минут/секунд (применимо не всегда)
- и так далее
Как тестировать React-приложение?
- Использовать фреймворк для тестирования, такой как Jest, для запуска и организации тестов. - Использовать библиотеку для моков, такую как Sinon.js, для мокирования зависимостей в тестах. - Написать модульные тесты для отдельных компонентов, чтобы убедиться, что они работают правильно изолированно. - Написать интеграционные тесты, чтобы проверить взаимодействие между компонентами. - Использовать snapshot-тестирование, чтобы гарантировать результат отрисовки компонента. - Использовать разработку через тестирование (TDD) для написания тестов перед реализацией функций. - Написать e2e тесты для тестирования приложения в целом, имитируя взаимодействие пользователя в реальном браузере.
Что такое ErrorBoundary?
ErrorBoundary
- это механизм перехвата ошибок. С их помощью можно обработать ошибку и дать пользователю обратную связь, если что-то пошло не так. В идеале - показать кнопку для повторения действия, в результате которого прозошла ошибка.
Почему этот код нестабилен?
this.setState({
counter: this.state.counter + this.props.increment,
});
Поскольку setState()
является асинхронным, установка нового состояния на основе предыдущего состояния иногда может пойти не так. В таких сценариях нужно использовать синтаксис функции обратного вызова для установки состояния. Тогда в prevState
будет актуальное значение.
this.setState((prevState, props) => {
return {
counter: prevState.counter + props.increment
}
})
Что такое reducer?
reducer
- это функция-преобразователь, принимающая на вход текущее состояние и идентификатор действия, которое необходимо совершить над этим состоянием.
Иными словами, reducer
возвращает модифицированную требуемым образом версию переданного ему состояния.
Параметром, идентифицирующим действие, обычно выступает объект с полями type
(название команды для изменения состояния) и payload
(полезная нагрузка - данные, необходимые для модификации).
Эта простая концепция лежит в основе большой части программных решений в рамках React.js для управления состоянием. Она может быть реализована как с использованием хука useReducer
, экспортируемого из React, так и в рамках, к примеру, Redux, где она является более развитой.
Вот так может выглядеть реализация функции-преобразователя (reducer):
export default function reducer(state, action) {
switch (action.type) {
case 'add':
return {
...state,
sum: state.sum + action.payload,
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
А вот так её использование:
import { useReducer } from 'react'
import reducer from './reducer'
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, sum: 0)
return (
<div>{state.sum}</div>
)
}
Хук useReducer
принимает на вход reducer
и начальное состояние, а возвращает текущее состояние и функцию dispatch
для обращения к reducer
.