Skip to main content

Вопросы по React. Версия 1

Источник.

Ключевые концепции React

Что такое React?

React - это открытая (с открытым исходным кодом) JavaScript-библиотека для фронтенда, предназначенная для создания пользовательских интерфейсов, особенно, если речь идет о создании одностраничных приложений (Single Page Applications, SPA). Она отвечает за слой представления (view) в веб и мобильных приложениях. React был разработан Jordan Walke - инженером Facebook. React был представлен на Facebook News Feed в 2011 году, а для Instagram - в 2012 году.

Назовите основные особенности React

Основными особенностями React является следующее:

  1. Использование VirtualDOM (виртуальной объектной модели документа) вместо RealDOM (настоящий или реальный DOM), поскольку манипуляции с RealDOM являются дорогостоящими с точки зрения производительности.
  2. Поддержка рендеринга на стороне сервера (Server Side Rendering, SSR).
  3. Следование принципу однонаправленного потока или связывания данных (one-directional data flow).
  4. Использование переиспользуемых (reusable) компонентов пользовательского интерфейса (User Interface, UI) для формирования слоя представления.

Что такое JSX?

JSX (JavaScript и XML) - это XML-подобный синтаксис, расширяющий возможности ECMAScript. По сути, он является синтаксическим сахаром для функции React.createElement, совмещая выразительность JavaScript с HTML-подобным синтаксисом разметки.

В приведенном ниже примере текст внутри тега h1 в методе render возвращается в виде JavaScript-функции:

class App extends React.Component {
render() {
return (
<div>
<h1>Добро пожаловать в мир React!</h1>
</div>
)
}
}

В чем разница между элементами и компонентами?

Элемент - это обычный объект, описывающий, что мы хотим увидеть на экране в терминах узлов и других частей DOM. Элементы могут содержать другие элементы в своих свойствах. Создавать элементы в React легко. Однако после создания, элемент не подлежит изменению.

Объектное представление React-элемента выглядит так:

const element = React.createElement(
'div',
{ id: 'login-btn' },
'Войти'
)

Функция React.createElement возвращает такой объект:

{
type: 'div',
props: {
children: 'Войти',
id: 'login-btn'
}
}

Данный объект рендерится с помощью ReactDOM.render():

<div id='login-btn'>Login</div>

В отличие от элемента, компонент может определяться по-разному. Он может быть классом с методом render (классовый компонент) или простой функцией (функциональный компонент). В любом случае компонент принимает свойства (пропы, props от properties) на вход и возвращает JSX:

const Button = ({ onLogin }) =>
<div id={'login-btn'} onClick={onLogin}>Войти</div>

JSX транспилируется (преобразуется) в функцию React.createElement:

const Button = ({ onLogin }) => React.createElement(
'div',
{ id: 'login-btn', onClick: onLogin },
'Войти'
)

Как создаются компоненты?

Существует 2 способа это сделать:

  1. Функциональные компоненты: это простейший способ создания компонента. Такие функции являются "чистыми" (pure), принимают объект с пропами в качестве аргумента и возвращают элемент (или несколько элементов во фрагменте):
function Greeting({ message }) {
return <h1>{`Привет, ${message}`}</h1>
}
  1. Классовые компоненты: для определения компонента также можно использовать ES6-классы. Приведенный функциональный компонент может быть переписан следующим образом:
class Greeting extends React.Component {
render() {
return <h1>{`Привет, ${this.props.message}`}</h1>
}
}

Когда лучше использовать классовый компонент, а когда функциональный?

Если компонент нуждается в состоянии или методах жизненного цикла, тогда используйте классовый компонент, иначе, используйте функциональный компонент.

Обратите внимание: в React 16.8 были представлены хуки, позволяющие использовать состояние, методы жизненного цикла и другие возможности классовых компонентов в функциях. В настоящее время классовые компоненты в React почти не используются.

Что такое "чистые" компоненты?

React.PureComponent - это то же самое, что React.Component, за исключением автоматической обработки метода shouldComponentUpdate(). При изменении пропов или состояния PureComponent автоматически выполняет их поверхностное (shallow) сравнение. Component такого сравнения по умолчанию не проводит. Поэтому компонент будет повторно рендерится до тех пор, пока не будет явно вызван метод shouldComponentUpdate().

Что такое состояние?

Состояние (state) - это объект, содержащий некоторую информацию, которая может измениться в течение жизненного цикла компонента. Мы всегда должны стараться делать состояние настолько простым, насколько это возможно, и минимизировать количество компонентов без состояния.

Создадим компонент User с состоянием message:

class User extends React.Component {
constructor(props) {
super(props)

this.state = {
message: 'Добро пожаловать в мир React!'
}
}

render() {
return (
<div>
<h1>{this.state.message}</h1>
</div>
)
}
}

state

Состояние похоже на проп, но оно является приватным (замкнуто в своей области видимости) и полностью контролируется компонентом, т.е. оно недоступно для других компонентов, кроме того, которому оно принадлежит и которое его определяет.

Что такое пропы?

Пропы (props) - это входные данные для компонента. Это простые значения (примитивы) или объект, содержащий несколько значений, которые передаются компонентам при их создании с помощью синтаксиса, похожего на атрибуты HTML-тегов.

Основное назначение пропов в React заключается в предоставлении компоненту следующего функционала:

  1. Передача данных компоненту.
  2. Вызов изменения состояния.
  3. Использование через this.props.propName внутри метода render компонента.

Создадим элемент со свойством reactProp:

<Element reactProp={1} />

Этот reactProp добавляется в качестве свойства ко встроенному объекту props, который присутствует во всех компонентах React:

props.reactProp

В чем разница между состоянием и пропами?

И props, и state являются обычными JavaScript-объектами. Несмотря на то, что они оба содержат информацию, которая используется при рендеринге компонента, функционал у них разный. Пропы передаются компоненту подобно аргументам, передаваемым функции, а состояние управляется компонентом как переменные, объявленные внутри функции.

Почему не следует обновлять состояние напрямую?

Если попытаться обновить состояние напрямую, компонент не будет подвергнут повторному рендерингу:

// неправильно
this.state.message = 'Привет, народ!'

Вместо этого, следует использовать метод setState. Он планирует (откладывает) обновление состояния компонента. Когда состояние меняется, компонент перерисовывается:

// правильно
this.setState({ message: 'Привет, народ!' })

Обратите внимание: состояние компонента можно изменять напрямую в constructor() или с помощью синтаксиса определения полей класса (данное предложение находится на 3 стадии рассмотрения).

Для чего в setState() используются функции обратного вызова?

Колбек вызывается после выполнения setState() и рендеринга компонента. Поскольку метод setState является асинхронным, колбек используется для выполнения любых "последующих" операций.

setState({ name: 'Иван' }, () => console.log(`Свойство "name" обновлено и компонент перерисован`))

Обратите внимание: вместо таких колбеков рекомендуется использовать методы жизненного цикла.

В чем разница между обработкой событий в HTML и React?

Вот некоторые из основных отличий:

  1. В HTML название события указывается строчными буквами (в нижнем регистре):
<button onclick="activateLasers()" />

В React для этого используется "верблюжий стиль" (camelCase):

<button onClick={activateLasers}>
  1. В HTML можно вернуть false для отключения поведения по умолчанию:
<a href='#' onclick="console.log('Ссылка!'); return false;" />

В React необходимо явно вызывать метод preventDefault:

function handleClick(event) {
event.preventDefault()
console.log('Ссылка!')
}
  1. В HTML необходимо вызывать функцию с помощью (). В React этого делать не нужно.

Как в колбеках JSX определяется контекст методов или обработчиков событий?

Существует 3 способа это сделать:

  1. Привязка в конструкторе - в классах JavaScript методы не связаны с экземплярами по умолчанию. То же самое справедливо для обработчиков событий в React. Обычно, мы делаем привязку в конструкторе:
class Component extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}

handleClick() {
// ...
}
}
  1. Синтаксис публичных полей класса - для привязки колбеков можно также использовать такой синтаксис:
handleClick = () => {
console.log('Это: ', this)
}
<button onClick={this.handleClick}>
Нажми на меня
</button>
  1. Стрелочные функции - также мы можем использовать стрелочные функции прямо в колбеках:
<button onClick={(event) => this.handleClick(event)}>
Нажми на меня
</button>

Обратите внимание: если колбек передается в качестве пропа дочерним компонентам, это может привести к ненужному повторному рендерингу. В таких случаях, рекомендуется использовать bind() или синтаксис публичных полей класса для улучшения производительности.

Как передать аргумент в колбек или обработчик событий?

Для этого можно использовать стрелочную функцию в качестве обертки для обработчика событий:

<button onClick={() => this.handleClick(id)} />

Это эквивалентно следующему вызову bind():

<button onClick={this.handleClick.bind(this, id)} />

В стрелочную функцию можно также передавать аргументы:

<button onClick={this.handleClick(id)} />
handleClick = (id) => () => {
console.log("Здравствуйте, ваш идентификатор: ", id)
}

Что такое синтетические события в React?

SyntheticEvent - это кроссбраузерная обертка для нативных событий браузера. Этот API аналогичен браузерному, включая stopPropagation() и preventDefault(), но работает одинаково во всех браузерах.

Что такое условный рендеринг?

Для условного рендеринга (conditional rendering) можно использовать обычные if или тернарные операторы. Кроме того, в JSX можно встраивать любое выражение посредством оборачивания его в фигурные скобки, а также совместно с логическим оператором && (короткие вычисления).

<h1>Привет!</h1>
{
messages.length > 0 && !isLogin
? (<h2>
У вас {messages.length} непрочитанных сообщений.
</h2>)
: (<h2>
У вас нет непрочитанных сообщений.
</h2>)
}

Что такое key и в чем заключаются преимущества его использования?

key - это специальный строковый атрибут, который следует использовать при создании списков элементов. Проп key помогает React определять, какие элементы подверглись изменениям, были добавлены или удалены.

Чаще всего в качестве ключа (key) мы используем идентификаторы:

const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)

При отсутствии id в качестве ключа можно использовать индекс элемента:

const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
)

Обратите внимание:

  1. Использовать индексы в качестве ключей не рекомендуется, если порядок расположения элементов может измениться. Это может негативно сказаться на производительности, а также привести к проблемам с состоянием компонента.
  2. При извлечении элемента списка в качестве самостоятельного компонента применяйте ключи к этим компонентам, а не к тегу li.
  3. При отсутствии пропа key в консоль будет выведено соответствующее предупреждение.

Для чего используются ссылки или рефы?

Реф (ref) возвращает ссылку на DOM-элемент. Этого в большинстве случаев следует избегать. Тем не менее, ссылки могут быть полезны при необходимости получения прямого доступа к DOM-элементу или экземпляру компонента.

Как создаются ссылки?

Существует 2 подхода:

  1. Новый: ссылки создаются с помощью React.createRef() и привязываются к элементу с помощью атрибута ref. Для того, чтобы иметь возможность использовать рефы во всем компоненте, просто присвойте ref свойству экземпляра в конструкторе:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return <div ref={this.myRef} />
}
}
  1. Также можно использовать реф-колбеки (callback refs). Например, доступ к полю для ввода текста строки для поиска можно получить следующим образом:
class SearchBar extends Component {
constructor(props) {
super(props)
this.txtSearch = null
this.state = { term: '' }
this.setInputSearchRef = (e) => {
this.txtSearch = e
}
}
onInputChange(event) {
this.setState({ term: this.txtSearch.value })
}
render() {
return (
<input
value={this.state.term}
onChange={this.onInputChange.bind(this)}
ref={this.setInputSearchRef}
/>
)
}
}

Рефы также могут использоваться в функциональных компонентах с помощью замыканий или хука useRef.

Обратите внимание: использовать встроенные реф-колбеки не рекомендуется.

Что такое перенаправление или передача ссылки?

Передача ссылки (ref forwarding) - это "фича" (feature - возможность, способность), которая позволяет компонентам принимать реф и передавать его потомкам (дочерним компонентам):

const ButtonElement = React.forwardRef((props, ref) => (
<button ref={ref} className="customButton">
{props.children}
</button>
))

// создаем ссылку на кнопку
const ref = React.createRef()
<ButtonElement ref={ref}>Передать ссылку</ButtonElement>

Что лучше использовать, колбек-рефы или findDOMNode()?

Лучше использовать callback refs. Это объясняется тем, что findDOMNode() препятствует будущим улучшениям React.

Типичный пример использования findDOMNode():

class MyComponent extends Component {
componentDidMount() {
findDOMNode(this).scrollIntoView()
}

render() {
return <div />
}
}

Рекомендуемый подход:

class MyComponent extends Component {
constructor(props){
super(props)
this.node = createRef()
}
componentDidMount() {
this.node.current.scrollIntoView()
}

render() {
return <div ref={this.node} />
}
}

Что такое виртуальный DOM?

Virtual DOM (VDOM) - это представление Real DOM, хранимое в памяти. Данное представление синхронизируется с настоящим DOM. Сравнение происходит между вызовом функции рендеринга и отображением элемента на экране. Данный внутренний процесс называется согласованием (reconciliation).

Как работает виртуальный DOM?

Virtual DOM работает следующим образом:

  1. При любом изменении внутренних данных пользовательский интерфейс меняется в представлении виртуального DOM.

vdom

  1. Затем вычисляется разница между предыдущим и новым представлениями.

vdom2

  1. После этого обновляются только те части реального DOM, которые подверглись изменениям.

vdom3

В чем разница между теневым и виртуальным DOM?

Shadow DOM - это браузерная технология, спроектированная для ограничения области видимости переменных и CSS в веб-компонентах. Virtual DOM - это концепция, реализуемая некоторыми библиотеками JavaScript поверх браузерных API.

Что такое React Fiber?

Fiber (волокно) - это новый движок согласования, изменение основного алгоритма, появившийся в React 16. Основной задачей React Fiber является повышения производительности в таких областях, как анимация, создание макета страницы, обработка жестов, возможность приостанавливать, прерывать или повторно запускать выполнение операций, предоставление приоритета определенным типам обновлений, а также новые примитивы параллелизма.

Для чего предназначен React Fiber?

Цель React Fiber - повышение производительности в таких областях, как анимация, создание макета страницы и обработка жестов. Основной его особенностью является incremental rendering (инкрементальный рендеринг; используется в Angular): возможность разделения процесса рендеринга на части и их объединение через различные фреймы.

Что такое управляемые компоненты?

Компоненты, которые управляют инпутами формы для текущего пользователя, называются управляемыми. Любое изменение состояния имеет соответствующий обработчик.

Например, для того, чтобы значение было представлено прописными буквами (в верхнем регистре), мы используем такой обработчик:

handleChange(event) {
this.setState({ value: event.target.value.toUpperCase() })
}

Что такое неуправляемые компоненты?

Неуправляемые компоненты - это компоненты, которые имеют собственное состояние. При необходимости получения значения их текущего состояния используются ссылки на DOM-элементы. Это больше похоже на обычный HTML.

В приведенном ниже примере мы получаем доступ к полю для ввода имени пользователя по ссылке:

class UserProfile extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.input = React.createRef()
}

handleSubmit(event) {
alert('Ваше имя: ' + this.input.current.value)
event.preventDefault()
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Имя:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
}

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

В чем разница между createElement() и cloneElement()?

JSX транспилируется в функции createElement для создания элементов React, которые используются для объектного представления пользовательского интерфейса. А cloneElement() используется для клонирования элемента и передачи ему новых свойств.

Что такое подъем состояния?

Когда несколько компонентов нуждаются в использовании одних и тех же изменяющихся данных, рекомендуется поднимать совместно используемое состояние до ближайшего общего предка. Это означает, что если два дочерних компонента используют одинаковые данные, следует поднять состояние к их родителю, вместо дублирования состояния в каждом потомке.

Назовите стадии жизненного цикла компонента

Жизненный цикл компонента состоит из 3 стадий:

  1. Монтирование: компонент готов к встраиванию в браузерный DOM. Эта стадия охватывает инициализацию в constructor(), а также методы жизненного цикла getDerivedStateFromProps, render и componentDidMount.

  2. Обновление: на данной стадии компонент обновляется либо из-за получения новых пропов, либо из-за обновления состояния с помощью setState() или forceUpdate(). Эта стадия охватывает такие методы жизненного цикла как getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate и componentDidUpdate.

  3. Размонтирование: на этой последней стадии компонент удаляется из браузерного DOM. Данная стадия включает метод жизненного цикла componentWillUnmount.

Следует отметить, что в React также имеются особые стадии применения изменений к DOM:

  1. Рендеринг: компонент рендерится без каких-либо побочных эффектов. Это применяется в отношении "чистых" компонентов. На данной стадии React может приостанавливать, прерывать или перезапускать рендеринг.

  2. Pre-commit: перед обновлением компонента существует момент, когда React читает DOM через getSnapshotBeforeUpdate().

  3. Commit: React изменяет DOM и выполняет завершающие методы жизненного цикла, такие как componentDidMount при монтировании, componentDidUpdate при обновлении и componentWillUnmount при размонтировании.

Стадии жизненного цикла в React 16.3+ (интерактивная версия)

phases 16.3+

До React 16.3:

phases 16.2

Назовите методы жизненного цикла

До React 16.3:

  • componentWillMount: выполняется перед рендерингом для настройки корневого компонента на уровне приложения;
  • componentDidMount: выполняется после первого рендеринга, здесь выполняются AJAX-запросы, обновляется DOM или состояние компонента, регистрируются обработчики событий;
  • componentWillReceiveProps: выполняется при обновлении любого пропа для запуска перехода состояния;
  • shouldComponentUpdate: определяет, должен ли компонент обновляться. Значением по умолчанию является true. Если компонент не нуждается в повторном рендеринге при изменении состояния или пропов, можно вернуть false. Это подходящее место для улучшения производительности, позволяющее предотвратить ненужные рендеринги при получении компонентом новых пропов;
  • componentWillUpdate: выполняется перед повторным рендерингом компонента при изменении состояния или пропов и при истинном значении, возвращаемом shouldComponentUpdate();
  • componentDidUpdate: в основном, используется для обновления DOM в соответствии с изменениями состояния или пропов;
  • componentWillUnmount: используется для отмены сетевых запросов или удаления обработчиков событий, связанных с компонентом.

React 16.3+:

  • getDerivedStateFromProps: запускается перед вызовом метода render и при каждом повторном рендеринге. Он используется в редких случаях, когда требуется производное состояние (derived state). Читать подробнее;
  • componentDidMount: выполняется после первого рендеринга, здесь выполняются AJAX-запросы, обновляется DOM или состояние компонента, регистрируются обработчики событий;
  • shouldComponentUpdate: определяет, должен ли компонент обновляться. Значением по умолчанию является true. Если компонент не нуждается в повторном рендеринге при изменении состояния или пропов, можно вернуть false. Это подходящее место для улучшения производительности, позволяющее предотвратить ненужные рендеринги при получении компонентом новых пропов;
  • getSnapshotBeforeUpdate: выполняется перед применением результатов рендеринга к DOM. Любое значение, возвращенное этим методом, передается в componentDidUpdate(). Это может быть полезным для получения информации из DOM, например, позиции курсора или величины прокрутки;
  • componentDidUpdate: в основном, используется для обновления DOM в соответствии с изменением состояния или пропов. Не выполняется, если shouldComponentUpdate() возвращает false;
  • componentWillUnmount используется для отмены сетевых запросов или удаления обработчиков событий, связанных с компонентом.

Что такое компонент высшего порядка?

Higher-order component (HOC) - это функция, принимающая компонент и возвращающая новый компонент. Это паттерн, производный от композиционной природы React.

Мы называем такие компоненты чистыми, поскольку они могут принимать и динамически предоставлять дочерние компоненты, но не меняют и не копируют их поведение.

const EnhancedComponent = higherOrderComponent(WrappedComponent)

HOC обычно используются для:

  1. Обеспечения возможности повторного использования кода, логики, а также для абстрагирования шаблонов.
  2. Отложенного рендеринга.
  3. Абстрагирования и манипуляции состоянием.
  4. Манипуляции пропами.

Как в HOC создаются прокси для пропов?

Пропы, передаваемые в компонент, можно добавлять/редактировать с помощью шаблона проксирования пропов следующим образом:

function HOC(WrappedComponent) {
return class Test extends Component {
render() {
const newProps = {
title: 'Новый заголовок',
footer: false,
showFeatureX: false,
showFeatureY: true
}

return <WrappedComponent {...this.props} {...newProps} />
}
}
}

Что такое контекст?

Context API предоставляет возможность передавать данные в дереве компонента без необходимости передачи пропов на каждом уровне вручную.

Например, статус аутентификации пользователя, языковые предпочтения или цветовая схема могут использоваться многими компонентами приложения:

const { Provider, Consumer } = React.createContext(defaultValue)

Что такое children?

Потомки (дети, дочерние компоненты) - это проп (this.props.children), позволяющий передавать одни компоненты другим как любые другие пропы. Дерево компонентов, размещаемое между открывающим и закрывающим тегами, передается компоненту в качестве пропа children.

Для работы с этим пропом в React API существуют такие методы как React.Children.map, React.Children.forEach, React.Children.count, React.Children.only и React.Children.toArray.

Простой пример использования пропа children:

const MyDiv = React.createClass({
render: function() {
return <div>{this.props.children}</div>
}
})

ReactDOM.render(
<MyDiv>
<span>Привет, </span>
<span>народ</span>
</MyDiv>,
node
)

Как в React выглядят комментарии?

Комментарии в React/JSX похожи на многострочные комментарии JavaScript, но оборачиваются в фигурные скобки:

Однострочные комментарии:

<div>
{/* Однострочный комментарий (в чистом `JavaScript` однострочные комментарии помещаются после двойного слеша (`//`)) */}
{`Добро пожаловать, ${user}! Приступим к изучению React`}
</div>

Многострочные комментарии:

<div>
{/*
Комментарий,
состоящий из
нескольких строк
*/}
{`Добро пожаловать, ${user}! Приступим к изучению React`}
</div>

Для чего используется super(props) в конструкторе?

Конструктор дочернего класса не может использовать ссылку на this до вызова метода super. То же самое справедливо для ES6-подклассов. Основная причина передачи аргумента props в super() состоит в обеспечении возможности доступа к this.props в конструкторе дочернего компонента.

Передача пропов:

class MyComponent extends React.Component {
constructor(props) {
super(props)

console.log(this.props) // { name: 'Иван', age: 30 }
}
}

Без пропов:

class MyComponent extends React.Component {
constructor(props) {
super()

console.log(this.props) // undefined

// однако, пропы по-прежнему доступны как в конструкторе,
console.log(props) // { name: 'Иван', age: 30 }
}

render() {
// так и за его пределами
console.log(this.props) // { name: 'Иван', age: 30 }
}
}

Что такое согласование?

После изменения состояния или пропов компонента, React определяет, нуждается ли DOM в обновлении посредством сравнения нового элемента с предыдущим. Когда эти элементы отличаются, React обновляет DOM. Данный процесс называется согласованием (reconciliation).

Как определить состояние с помощью вычисляемого свойства?

При использовании ES6 или транспилятора Babel для преобразования JSX-кода можно применять вычисляемые названия свойств:

handleInputChange(event) {
this.setState({ [event.target.id]: event.target.value })
}

Какая распространенная ошибка приводит к вызову функции при каждом рендеринге?

При передаче функции компоненту в качестве аргумента она не должна вызываться:

render() {
// неправильно: `handleClick` вызывается вместо передачи в качестве ссылки
return <button onClick={this.handleClick()}>Нажми на меня</button>
}

Вместо этого, следует передавать саму функцию, без круглых скобок (()):

render() {
// правильно: `handleClick` передается как ссылка
return <button onClick={this.handleClick}>Нажми на меня</button>
}

Поддерживает ли React.lazy() именованный экспорт?

Нет, функция React.lazy поддерживает только экспорт по умолчанию. Для того, чтобы импортировать модуль с помощью именованного импорта, можно создать промежуточный модуль, который будет повторно экспортировать этот модуль по умолчанию. Это также обеспечит тряску дерева (tree shaking) и предотвратит загрузку неиспользуемых компонентов.

Рассмотрим файл, в котором по имени экспортируется несколько компонентов:

// MoreComponents.js
export const SomeComponent = /* ... */
export const UnusedComponent = /* ... */

Затем компоненты из MoreComponents.js повторно экспортируются в промежуточном файле IntermediateComponent.js:

// IntermediateComponent.js
export { SomeComponent as default } from "./MoreComponents.js"

После этого компоненты могут импортироваться лениво:

import React, { lazy } from 'react'
const SomeComponent = lazy(() => import("./IntermediateComponent.js"))

Почему в React используется className вместо class?

class - это одно из ключевых слов JavaScript, а JSX - это расширение JavaScript. Вот почему в React вместо class используется className. В качестве пропа className передается строка:

render() {
return <span className='nav nav-main'>Меню</span>
}

Что такое фрагмент?

Фрагмент (fragment) - это распространенный паттерн в React, который используется в компонентах, возвращающих несколько элементов. Фрагменты позволяют группировать дочерние элементы без создания лишних DOM-узлов:

render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
)
}

Также существует сокращенный синтаксис, но он не поддерживается в некоторых библиотеках (на сегодняшний день таких библиотек почти не осталось):

render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}

Почему фрагмент лучше, чем дополнительный div?

Вот некоторые причины:

  1. Фрагменты немного быстрее и используют меньше памяти. Реальная польза от этого ощущается в очень больших и глубоких деревьях элементов.
  2. Некоторые механизмы CSS, например, Flexbox и Grid используют связь предок-потомок, поэтому добавление дополнительных div может поломать макет страницы.
  3. Меньшее количество элементов облегчает инспектирование DOM.

Что такое порталы?

Порталы (portals) - рекомендуемый способ рендеринга потомков в DOM-узле, который находится за пределами родительского компонента:

ReactDOM.createPortal(child, container)

Первый аргумент - любой React-компонент, подлежащий рендерингу, такой как элемент, строка или фрагмент. Второй аргумент - DOM-элемент.

Что такое компонент без состояния?

Если поведение компонента не зависит от его состояния, такой компонент считается не имеющим состояния. Для создания компонентов без состояния можно использовать как функции, так и классы. Однако, если нам не нужны методы жизненного цикла, тогда лучше использовать функциональные компоненты. У функций по сравнению с классами в этом случае имеется несколько преимуществ: их легче писать, читать и тестировать, они немного быстрее и в них не требуется использовать ключевое слово this.

Что такое компонент с состоянием?

Если поведение компонента зависит от состояния, такой компонент считается имеющим состояние. Раньше компоненты с состоянием можно было создавать только с помощью классов. Сейчас хуки предоставляют возможность создавать функциональные компоненты с состоянием. В классах состояние инициализируется в constructor() с помощью this.state = initialValue, в функции - с помощью const [state, setState] = useState(initialValue).

Классовый компонент с состоянием:

class App extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}

render() {
// ...
}
}

Аналогичный функциональный компонент:

import React, { useState } from 'react'

const App = (props) => {
const [count, setCount] = useState(0)

return (
// ...
)
}

Как осуществить проверку пропов?

При запуске приложения в режиме для разработки, React автоматически проверяет все пропы, определенные в компонентах, на правильность типов. Если тип является неправильным, React выводит в консоль предупреждение. Эта проверка отключена в режиме для продакшна с целью повышения производительности. Обязательные пропы определяются с помощью isRequired.

Набор предопределенных типов пропов:

  1. PropTypes.number.
  2. PropTypes.string.
  3. PropTypes.array.
  4. PropTypes.object.
  5. PropTypes.func.
  6. PropTypes.node.
  7. PropTypes.element.
  8. PropTypes.bool.
  9. PropTypes.symbol.
  10. PropTypes.any.

Пример определения propTypes для компонента User:

import React from 'react'
import PropTypes from 'prop-types'

class User extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
}

render() {
return (
<>
<h1>{`Добро пожаловать, ${this.props.name}!`}</h1>
<h2>{`Ваш возраст: ${this.props.age}`}</h2>
</>
)
}
}

Обратите внимание: В React 15.5 PropTypes были перемещены из React.PropTypes в библиотеку prop-types.

Какие преимущества предоставляет использование React?

Вот список основных преимуществ:

  1. Повышение производительности приложения благодаря виртуальному DOM.
  2. JSX облегчает написание и чтение кода.
  3. Возможность рендеринга как на стороне клиента, так и на стороне сервера.
  4. Возможность относительно простой интеграции с фреймворками, поскольку React - это всего лишь библиотека.
  5. Возможность быстрого юнит и интеграционного тестирования с помощью таких инструментов, как Jest.

Какие ограничения имеются в React?

Кроме преимуществ, в React существуют некоторые ограничения:

  1. React - это всего лишь библиотека, отвечающая за слой представления, а не полноценный фреймворк.
  2. Его трудно изучать новичкам в веб-разработке (впрочем, как и любой другой фреймворк).
  3. Интеграция с традиционными MVC-фреймворками требует дополнительных настроек.
  4. Код является более сложным из-за встроенных шаблонов и JSX.
  5. Большое количество мелких компонентов приводит к сложности в проектировании и построении архитектуры приложения.

Что такое предохранители?

Предохранители (error boundaries) - это компоненты, которые отлавливают ошибки JavaScript, возникающие в любом дочернем компоненте, сообщают об этих ошибках и отображают резервный UI.

Классовый компонент становится предохранителем при определении в нем метода жизненного цикла componentDidCatch или метода static getDerivedStateFromError:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}

componentDidCatch(error, info) {
// отправляем ошибки в специальный сервис
logErrorToMyService(error, info)
}

static getDerivedStateFromError(error) {
// обновляем состояние, чтобы при следующем рендеринге использовался резервный `UI`
return { hasError: true }
}

render() {
if (this.state.hasError) {
// резервный `UI`
return <h1>Что-то пошло не так.</h1>
}
return this.props.children
}
}

После этого предохранитель используется как обычный компонент:

<ErrorBoundary>
<MyWidget />
</ErrorBoundary>

Как реализовать предохранитель в React 15?

React 15 предоставляет поддержку для error boundaries с помощью метода unstable_handleError. Данный метод был переименован в componentDidCatch в React 16.

Какой способ рекомендуется использовать для статической проверки типов?

Обычно, для проверки типов в React-приложениях используется библиотека PropTypes (React.PropTypes перемещен в пакет prop-types, начиная с React 15.5). Для больших приложений рекомендуется использовать статические типизаторы, такие как Flow или TypeScript, которые выполняют проверку типов во время компиляции и предоставляют возможности по их автоматическому исправлению.

Для чего используется пакет react-dom?

Пакет react-dom предоставляет методы, специфичные для DOM, которые могут быть использованы на верхнем уровне приложения. Большинству компонентов не нужен этот модуль. Вот некоторые из методов рассматриваемой библиотеки:

  1. render.
  2. hydrate.
  3. unmountComponentAtNode.
  4. findDOMNode.
  5. createPortal.

Для чего предназначен метод render?

Данный метод используется для встраивания React-элемента в DOM в определенный контейнер и возврата ссылки на этот элемент. Если React-элемент уже рендерился ранее, осуществляется обновление только той части DOM, которая подверглась соответствующим изменениям.

ReactDOM.render(element, container[, callback])

При передаче render() опционального колбека, он будет вызван после рендеринга или обновления компонента.

Что такое ReactDOMServer?

ReactDOMServer - это объект, позволяющий рендерить компоненты в виде статической разметки (обычно, используется на Node.js-серверах). Данный объект в основном используется при рендеринге на стороне сервера. Следующие методы могут быть использованы как на сервере, так и в браузерном окружении:

  1. renderToString.
  2. renderToStaticMarkup.

Например, мы запускаем основанный на Node.js веб-сервер, такой как Express, Koa или Happi, и вызываем renderToString() для рендеринга корневого элемента в виде строки, которую сервер отправляет в ответ на запрос:

// используем `Express`
import { renderToString } from 'react-dom/server'
import MyPage from './MyPage'

app.get('/', (req, res) => {
res.write('<!DOCTYPE html><html><head><title>My Page</title></head><body>')
res.write('<div id="content">')
res.write(renderToString(<MyPage/>))
res.write('</div></body></html>')
res.end()
})

Как использовать innerHtml в React?

Атрибут dangerouslySetInnerHTML в React является альтернативой свойства innerHTML. Как и последний, его использование представляет собой угрозу межсайтового скриптинга (XSS). В dangerouslySetInnerHTML необходимо передать объект с ключом __html и HTML-разметкой в качестве значения.

В приведенном ниже примере MyComponent использует атрибут dangerouslySetInnerHTML для определения разметки:

function createMarkup() {
return { __html: 'Опасно!' }
}

function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />
}

Как использовать стили в React?

Атрибут style принимает объект JavaScript со свойствами в стиле camelCase, а не в виде CSS-строки. Это обеспечивает согласованность с JavaScript-свойствами, связанными со стилями, является более эффективным и закрывает некоторые дыры в безопасности (XSS).

const divStyle = {
color: 'deepskyblue',
backgroundImage: 'url(' + imgUrl + ')'
}

function HelloWorldComponent() {
return <div style={divStyle}>Привет, народ!</div>
}

Стили пишутся в camelCase для интеграции со свойствами узлов DOM в JavaScript (например, node.style.backgroundImage).

Чем отличаются события в React?

Обработка событий в элементах React имеет некоторые синтаксические отличия:

  1. Обработчики событий именуются в верблюжьем стиле (onClick), а не в нижнем регистре (onclick).
  2. В JSX в обработчик передается функция, а не строка (функция передается как ссылка, т.е. не вызывается).

Что произойдет при использовании setState() в constructor()?

Если это сделать, то, помимо присвоения значения объекту состояния, React повторно отрендерит компонент и всех его потомков. Мы получим ошибку Can only update a mounted or mounting component (можно обновлять только смонтированный или монтируемый компонент). Поэтому для инициализации состояния внутри конструктора следует использовать this.state.

Почему следует избегать использования индексов в качестве ключей компонентов?

Ключи (keys) должны быть стабильными, предсказуемыми и уникальными, чтобы React имел возможность "следить" за элементами.

В приведенном ниже примере ключом каждого элемента является порядок его расположения в массиве без привязки к предоставляемым им данным. Это ограничивает возможности React по оптимизации:

{todos.map((todo, index) =>
<Todo
{...todo}
key={index}
/>
)}

При использовании в качестве ключей некоторых данных элемента, например, todo.id, которые являются уникальными и стабильными, у React появляется возможность менять порядок расположения (рендеринга) элементов без необходимости выполнения дополнительных вычислений:

{todos.map((todo) =>
<Todo {...todo} key={todo.id} />
)}

Правильно ли использовать setState() в componentWillMount()?

Да, использование setState() внутри componentWillMount() является безопасным. В то же время, рекомендуется избегать выполнения асинхронной инициализации в методе жизненного цикла componentWillMount. componentWillMount() вызывается перед монтированием. Он также вызывается перед методом render, поэтому настройка состояния в этом методе не приводит к повторному рендерингу. В нем следует избегать выполнения каких-либо побочных эффектов и подписок. Вместо componentWillMount() асинхронную логику следует помещать в componentDidMount().

componentDidMount() {
axios.get(`/api/todos`)
.then(({ data }) => {
this.setState({
messages: [...data]
})
})
.catch(console.error)
}

Что произойдет при использовании пропов в constructor()?

При изменении пропов компонента без его обновления, эти новые пропы никогда не будут отображены на экране, поскольку функция-конструктор никогда не обновит текущее состояние компонента. Обновление состояния через пропы возможно только после создания компонента.

В приведенном ниже примере обновленное значение инпута никогда не отобразится:

class MyComponent extends React.Component {
constructor(props) {
super(props)

this.state = {
records: [],
inputValue: this.props.inputValue
}
}

render() {
return <div>{this.state.inputValue}</div>
}
}

Значение обновится при использовании пропов в методе render:

class MyComponent extends React.Component {
constructor(props) {
super(props)

this.state = {
record: []
}
}

render() {
return <div>{this.props.inputValue}</div>
}
}

Как выполнить условный рендеринг компонента?

В некоторых случаях требуется рендерить разные компоненты в зависимости от некоторого состояния. JSX не рендерит false или undefined, поэтому для рендеринга определенной части компонента только при удовлетворении определенного условия можно использовать короткие вычисления (оператор &&):

const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address && <p>{address}</p>}
</div>
)

Если нам требуется условие if else, тогда можно воспользоваться тернарным оператором:

const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address
? <p>{address}</p>
: <p>Адрес отсутствует</p>
}
</div>
)

Почему следует быть осторожным при распаковке пропов на DOM-элементы?

При распространении или распаковке пропов мы подвержены риску добавления неизвестных HTML-атрибутов, что считается плохой практикой. Вместо этого, лучше использовать деструктуризацию пропов с помощью оператора ...rest. Это обеспечит добавление к элементу только нужных пропов:

Например:

const ComponentA = () =>
<ComponentB isDisplay={true} className='someClassName' />

const ComponentB = ({ isDisplay, ...domProps }) =>
<div {...domProps}>{ComponentB}</div>

Как использовать декораторы в React?

Классовые компоненты могут декорироваться способом, аналогичным передаче компонента в функцию. Decorators - гибкий и удобочитаемый способ модификации функциональности компонента:

@setTitle('Профиль')
class Profile extends React.Component {
//....
}

/*
`title` - это строка, которая устанавливается заголовку документа
`WrappedComponent` - это то, что получит декоратор
при его размещении над классовым компонентом
*/
const setTitle = (title) => (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
document.title = title
}

render() {
return <WrappedComponent {...this.props} />
}
}
}

Обратите внимание: декораторы - экспериментальная технология, находящаяся на 3 стадии рассмотрения.

Как запомнить или сохранить компонент?

Существуют специальные библиотеки для запоминания или сохранения компонентов для предотвращения их повторного вычисления. Данная техника называется мемоизацией.

Например, библиотека moize может сохранять один компонент в другом:

import moize from 'moize'
import Component from './components/Component'

const MemoizedFoo = moize.react(Component)

const Consumer = () => {
<div>
<MemoizedFoo />
</div>
}

Начиная с React 16.6, у нас имеется React.memo(). Это компонент высшего порядка (HOC), который сохраняет компонент неизменным до обновления его пропов. Для этого достаточно обернуть в него мемоизируемый компонент:

const MemoComponent = React.memo(function MemoComponent(props) {
/* рендеринг с помощью пропов */
})
// или
export default React.memo(MyFunctionComponent)

Также следует отметить, что похожий (но не аналогичный) функционал предоставляет хук useMemo.

Как в React реализовать рендеринг на стороне сервера?

React поддерживает рендеринг на стороне Node.js-сервера из коробки. Для этого используется специальная версия DOM-рендерера, которая реализует такой же паттерн, что и клиентская часть приложения:

import ReactDOMServer from 'react-dom/server'
import App from './App'

ReactDOMServer.renderToString(<App />)

Метод renderToString возвращает обычный HTML в виде строки, которая затем может быть помещена в тело (body) ответа сервера. На стороне клиента React определяет предварительно отрендеренный контент и просто вставляет его в существующее дерево компонентов.

Как включить производственный режим?

Для этого следует установить NODE_ENV в значение production в методе DefinePlugin() сборщика модулей Webpack. Это отключит проверку типов и дополнительные предупреждения. Кроме того, при минификации кода, например, с помощью Uglify, удаляющего "мертвый" код, а также код и комментарии для разработки, размер сборки будет намного меньше.

Что такое Create React App и в чем заключаются преимущества его использования?

create-react-app - это инструмент, позволяющий быстро создавать и запускать React-приложения, минуя стадию настройки проекта.

Создадим проект приложения (todo-app) с помощью CRA:

npx create-react-app todo-app
# или
npm init react-app todo-app
# или
yarn create react-app todo-app

cd todo-app

# сборка, тестирование и запуск сервера для разработки
$ npm run build
$ npm run test
$ npm start
# или
$ yarn build
$ yarn test
$ yarn start

Проект включает в себя все необходимое для разработки приложения на React:

  1. Поддержка синтаксиса ES6, React, JSX и Flow.
  2. Расширения языка после ES6, такие как spread- и rest-операторы.
  3. Автоматическое добавление префиксов к стилям, поэтому нам не нужно вручную добавлять -webkit- и другие префиксы.
  4. Быстрый запуск интерактивных юнит-тестов со встроенной поддержкой отчетов о покрытии кода.
  5. Автоматически перезапускаемый сервер для разработки, выводящий предупреждения о наиболее распространенных ошибках.
  6. Скрипт для сборки JS, CSS и статических файлов для продакшна с хешами в названиях и картами ресурсов (source maps).

Назовите методы жизненного цикла компонента, относящиеся к монтированию

При создании и встраивании компонента в DOM методы жизненного цикла вызываются в следующем порядке:

  1. constructor.
  2. static getDerivedStateFromProps.
  3. render.
  4. componentDidMount.

Какие методы жизненного цикла были признаны устаревшими в React 16?

Следующие методы жизненного цикла являются небезопасными практиками кодирования и усложняют работу с асинхронной логикой:

  1. componentWillMount.
  2. componentWillReceiveProps.
  3. componentWillUpdate.

Начиная с React 16.3, эти методы следует использовать с префиксом UNSAFE_, а версии без префиксов были удалены в React 17.

Для чего используется метод жизненного цикла getDerivedStateFromProps?

Статический метод жизненного цикла getDerivedStateFromProps вызывается после инстанцирования элемента перед его повторным рендерингом. Он может возвращать объект для обновления состояния или null как индикатор того, что новые пропы не требуют обновления состояния.

class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}

Этот метод вместе с componentDidUpdate() охватывает все случаи использования componentWillReceiveProps().

Для чего используется метод жизненного цикла getSnapshotBeforeUpdate?

Метод жизненного цикла getSnapshotBeforeUpdate вызывается сразу после обновления DOM. Значение, возвращенное этим методом, передается в качестве третьего аргумента componentDidUpdate().

class MyComponent extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}

Этот метод вместе с componentDidUpdate() охватывает все случаи использования componentWillUpdate()

Заменяют ли хуки рендер-пропы и компоненты высшего порядка?

Рендер-пропы (render props) и компоненты высшего порядка рендерят только одного потомка, однако, в большинстве случаев, хуки предоставляют более простой способ минимизации количества уровней вложенности дерева компонентов.

Как рекомендуется именовать компоненты?

Компоненты рекомендуется именовать с помощью ссылок вместо использования displayName.

Пример использования displayName:

export default React.createClass({
displayName: 'TodoApp',
// ...
})

Рекомендуемый подход:

export default class TodoApp extends React.Component {
// ...
}

Какой порядок расположения методов в классовом компоненте является рекомендуемым?

Рекомендуемый порядок расположения методов от монтирования до рендеринга классового компонента следующий:

  1. static методы.
  2. constructor.
  3. getChildContext.
  4. componentWillMount.
  5. componentDidMount.
  6. componentWillReceiveProps.
  7. shouldComponentUpdate.
  8. componentWillUpdate.
  9. componentDidUpdate.
  10. componentWillUnmount.
  11. Обработчики событий, такие как handleSubmit или handleChange.
  12. Геттеры, необходимые для рендеринга, такие как getSelectReason или getFooterContent.
  13. Опциональные методы рендеринга, такие как renderNavigation или renderProfilePicture.
  14. render.

Что такое компонент-переключатель?

Компонент-переключатель (switching component) - это компонент, который рендерит один из нескольких компонентов. Для получения значений пропов для компонентов необходимо использовать объект.

Пример компонента-переключателя, отображающего разные страницы на основе пропа page:

import HomePage from './HomePage'
import AboutPage from './AboutPage'
import ServicesPage from './ServicesPage'
import ContactPage from './ContactPage'

const PAGES = {
home: HomePage,
about: AboutPage,
services: ServicesPage,
contact: ContactPage
}

const Page = (props) => {
const CurrentPage = PAGES[props.page] || HomePage

return <CurrentPage {...props} />
}

// ключи объекта `PAGES` могут быть использованы в `propTypes`
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired
}

Для чего в setState() может передаваться функция?

Дело в том, что setState() - это асинхронная операция. React откладывает обновление состояния по причинам производительности, поэтому состояние может обновиться не сразу после вызова setState(). Это означает, что нам не следует полагаться на текущее состояние при вызове setState(), поскольку мы не можем быть увереными в том, каким оно является. Решением данной проблемы является передача в setState() функции с предыдущим состоянием в качестве аргумента. Это позволяет избежать проблем, связанных с отображением на экране старого состояния компонента из-за асинхронной природы setState().

Допустим, начальным значением count является 0. Несмотря на 3 последовательных вызова операции по увеличению значения, count равняется 1:

// предположим, что `this.state.count === 0`
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
// `this.state.count === 1`, а не `3`!

Если мы передадим функцию в setState() значение count увеличится корректно:

this.setState((prevState, props) => ({
count: prevState.count + props.increment
}))
// `this.state.count === 3`, как ожидается

Почему в setState() рекомендуется использовать функцию, а не объект?

React может объединять несколько вызовов setState() в один для повышения производительности. Поскольку this.props и this.state обновляются асинхронно, нам не следует полагаться на их значения для вычисления следующего состояния.

Приведенный ниже пример счетчика работает неправильно:

// неправильно
this.setState({
counter: this.state.counter + this.props.increment,
})

Рекомендуемый подход заключается в вызове setState() с функцией в качестве аргумента. Эта функция принимает предыдущее состояние в качестве первого параметра и обновленные пропы в качестве второго параметра:

// правильно
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))

Что такое строгий режим в React?

React.StrictMode - это полезный компонент, обеспечивающий индикацию потенциальных проблем в приложении. Как и <Fragment>, <StrictMode> не приводит к рендеренгу лишних DOM-элементов. Он активирует дополнительные проверки и предупреждения. Эти проверки применяются только в режиме для разработки.

import React from 'react'

function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
)
}

В приведенном примере строгий режим включен только для компонентов ComponentOne и ComponentTwo.

Что такое примеси?

Примеси или миксины (mixins) - это способ обеспечения совместного использования одной функциональности совершенно разными компонентами. Примеси не следует использовать, их можно заменить компонентами высшего порядка или декораторами.

Одним из самых распространенных случаев использования примесей является PureRenderMixin. Его можно использовать в некоторых компонентах для предотвращения повторного рендеринга, когда пропы и состояние поверхностно (shallow) равны предыдущим:

const PureRenderMixin = require('react-addons-pure-render-mixin')

const Button = React.createClass({
mixins: [PureRenderMixin],
// ...
})

В настоящее время примеси признаны устаревшими.

Почему isMounted() является антипаттерном? Назовите более подходящее решение

Основная цель использования isMounted() заключается в предотвращении вызова setState() после размонтирования компонента, поскольку это приводит к выводу в консоль предупреждения.

if (this.isMounted()) {
this.setState({ ... })
}

Проверка isMounted() перед вызовом setState() отключает предупреждения, но противоречит цели этих предупреждений. Использование isMounted() - это дурно пахнущий код, поскольку единственная причина его использования заключается в предположении, что нам потребуется ссылка на компонент после его размонтирования.

Оптимальным решением является определение места, где setState() может быть вызван после размонтирования компонента, и его удаление. Такие ситуации обычно возникают в колбеках, когда компонент ожидает получения некоторых данных и размонтируется до их получения. В идеале, все колбеки должны отключаться в componentWillUnmount() перед размонтированием компонента.

Какие события указателя поддерживаются в React?

События указателя (Pointer Events) предоставляют унифицированный способ обработки всех событий ввода. Раньше у нас была мышь и соответствующие обработчики, сегодня у нас имеется множество различных устройств, в состав которых мышь не входит, например, телефоны с событиями касаний экрана пальцем или стилусом. Необходимо помнить, что эти события работают только в браузерах, поддерживающих соответствующую спецификацию.

Следующие типы событий доступны в React DOM:

  1. onPointerDown.
  2. onPointerMove.
  3. onPointerUp.
  4. onPointerCancel.
  5. onGotPointerCapture.
  6. onLostPointerCapture.
  7. onPointerEnter.
  8. onPointerLeave.
  9. onPointerOver.
  10. onPointerOut.

Почему название компонента должно начинаться с большой буквы?

Когда мы рендерим компонент с помощью JSX, название этого компонента должно начинаться с большой буквы, в противном случае, React выбросит исключение, связанное с неопознанным HTML-тегом. Принятое соглашение гласит, что с маленькой буквы могут начинаться только HTML и SVG-теги.

class SomeComponent extends Component {
// ...
}

Мы можем определить классовый компонент, название которого начинается с маленькой буквы, но при импорте название должно начинаться с большой буквы. Здесь название, начинающееся с маленькой буквы, не вызовет проблем:

class myComponent extends Component {
render() {
return <div />
}
}

export default myComponent

При импорте указанного компонента в другой файл, его название должно начинаться с большой буквы:

import MyComponent from './MyComponent'

Существуют ли исключения из этого правила?

Названия компонентов должны начинаться с большой буквы, за одним исключением: имена тегов, начинающиеся с маленькой буквы, с точкой (аксессоры) являются валидными названиями компонентов.

Например, следующий тег будет скомпилирован в валидный компонент:

render(){
return <obj.component /> // `React.createElement(obj.component)`
}

Поддерживаются ли пользовательские DOM-атрибуты в React 16?

Да. В прошлом React игнорировал неизвестные атрибуты. Когда мы использовали атрибут, который React не мог распознать, он просто его пропускал.

Например, элемент с таким атрибутом:

<div mycustomattribute='something' />

в React 15 превращался в пустой div:

<div />

В React 16 любые неизвестные атрибуты все равно встраиваются в DOM:

<div mycustomattribute='something' />

Это полезно с точки зрения использования специфичных для браузера или библиотеки атрибутов, тестирования новых DOM API и интеграции с фреймворками.

В чем разница между constructor() и getInitialState()?

При использовании ES6-классов состояние следует инициализировать в конструкторе, а при использовании React.createClass() - в методе getInitialState.

ES6-классы:

class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { /* начальное состояние */ }
}
}

React.createClass():

const MyComponent = React.createClass({
getInitialState() {
return { /* начальное состояние */ }
}
})

Обратите внимание: React.createClass() признан устаревшим и удален в React 16. Для создания компонентов с состоянием используйте классы или функции с хуками.

Можно ли принудительно обновить компонент без вызова setState()?

По умолчанию компонент перерисовывается при изменении его состояния или пропов. Если метод render зависит от других данных, мы можем сообщить React о том, что компонент нуждается в повторном рендеринге, вызвав forceUpdate():

component.forceUpdate(callback)

Обратите внимание: использовать forceUpdate() не рекомендуется.

В чем разница между super() и super(props)?

Если мы хотим получить доступ к this.props в constructor(), тогда пропы должны быть переданы в метод super.

super(props):

class MyComponent extends React.Component {
constructor(props) {
super(props)
console.log(this.props) // { name: 'Иван', ... }
}
}

super():

class MyComponent extends React.Component {
constructor(props) {
super()
console.log(this.props) // undefined
}
}

Как реализовать цикл внутри JSX?

Для этого можно использовать Array.prototype.map() и стрелочную функцию.

Например, в следующем примере массив объектов items преобразуется в массив компонентов:

<tbody>
{items.map(item => <TableRow key={item.id} name={item.name} />)}
</tbody>

Однако, с помощью for реализовать цикл не получится:

// не работает
<tbody>
for (let i = 0; i < items.length; i++) {
<TableRow key={items[i].id} name={items[i].name} />
}
</tbody>

Это объясняется тем, что JSX-теги транспилируются в вызовы функций, а мы не можем использовать операторы внутри выражений. Ситуация может измениться благодаря выражению do, которое находится на 1 стадии рассмотрения.

Как получить доступ к пропам в закавыченных значениях атрибутов?

JSX не поддерживает интерполяцию переменных в значениях атрибутов. Следующий код не будет работать:

<img className='image' src='images/{this.props.image}' />

Однако, мы можем поместить любое JavaScript-выражение в фигурные скобки как входящее значение атрибута. Следующий код работает, как ожидается:

<img className='image' src={'images/' + this.props.image} />

Также можно использовать шаблонные литералы (template literals):

<img className='image' src={`images/${this.props.image}`} />

Что такое массив PropTypes.shape?

Если мы хотим передать массив объектов определенной формы в компонент, тогда следует использовать PropTypes.shape() в качестве аргумента PropTypes.arrayOf():

ReactComponent.propTypes = {
arrayWithShape: PropTypes.arrayOf(PropTypes.shape({
color: PropTypes.string.isRequired,
fontSize: PropTypes.number.isRequired
})).isRequired
}

Как реализовать условное применение CSS-класса?

Не следует использовать фигурные скобки внутри кавычек, поскольку в этом случае они будут оцениваться как строка:

// не работает
<div className="btn-panel {this.props.visible ? 'show' : 'hidden'}">

Вместо этого, фигурные скобки следует помещать снаружи (не забудьте про пробел между названиями классов):

<div className={'btn-panel ' + (this.props.visible ? 'show' : 'hidden')}>

Лучше использовать шаблонные строки:

<div className={`btn-panel ${this.props.visible ? 'show' : 'hidden'}`}>

В чем разница между React и ReactDOM?

Пакет react содержит React.createElement(), React.Component, React.Children и другие вспомогательные функции, связанные с элементами и компонентами. О них можно думать как об изоморфных или универсальных помощниках в создании компонентов. Пакет react-dom содержит ReactDOM.render(), а в react-dom/server у нас имеется рендеринг на стороне сервера, обеспечиваемый такими методами как ReactDOMServer.renderToString() и ReactDOMServer.renderToStaticMarkup().

Почему ReactDOM отделен от React?

Команда React проделала большую работу по извлечению всех "фич", связанных с DOM, в отдельную библиотеку под названием react-dom. Впервые библиотеки были разделены в React 0.14. Учитывая другие библиотеки, такие как react-native, react-art, react-canvas и react-three, становится очевидным, что ядро React не должно быть тесно связано с браузером или DOM.

Для обеспечения рендеринга в разных средах выполнения кода, команда React разделила основной пакет React на две части: react и react-dom. Это позволяет легко создавать компоненты, которые могут использоваться как в веб, так и в мобильной версиях приложения.

Как использовать элемент label в React?

Если мы попытаемся отрендерить элемент label, привязав его к инпуту с помощью стандартного атрибута for, то в готовой разметке этот атрибут будет пропущен, а в консоль будет выведено предупреждение:

<label for='user'>Пользователь</label>
<input type='text' id='user' />

Поскольку for в JavaScript является ключевым словом, вместо него следует использовать htmlFor:

<label htmlFor='user'>Пользователь</label>
<input type='text' id='user' />

Как совместно использовать несколько встроенных объектов со стилями?

Для этого можно использовать spread-оператор в обычном React:

  <button style={{...styles.panel.button, ...styles.panel.submitButton}}>Отправить</button>

В React Native можно использовать синтаксис массивов:

<button style={[styles.panel.button, styles.panel.submitButton]}>Отправить</button>

Как повторно отрендерить компонент при изменении окна просмотра браузера?

Для этого можно прослушивать событие resize в методе componentDidMount и обновлять направления (width и height). Не забудьте удалить соответствующий обработчик в методе componentWillUnmount:

class WindowDimensions extends React.Component {
constructor(props){
super(props)
this.updateDimensions = this.updateDimensions.bind(this)
}

componentWillMount() {
this.updateDimensions()
}

componentDidMount() {
window.addEventListener('resize', this.updateDimensions)
}

componentWillUnmount() {
window.removeEventListener('resize', this.updateDimensions)
}

updateDimensions() {
this.setState({width: window.innerWidth, height: window.innerHeight})
}

render() {
return <span>{this.state.width} x {this.state.height}</span>
}
}

Аналогичный функциональный компонент:

function WindowDimensions() {
const [state, setState] = useState({
width: window.innerWidth,
height: window.innerHeight
})

useEffect(() => {
function updateDimensions() {
setState({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', updateDimensions)
return () => {
window.removeEventListener('resize', updateDimensions)
}
}, [])

return <span>{state.width} x {state.height}</span>
}

В чем разница между методами setState и replaceState?

При использовании setState() текущее и предыдущее состояния объединяются. replaceState() удаляет текущее состояние и заменяет его переданным. Обычно, setState() используется до тех пор, пока нам действительно не понадобится удалить все предыдущие ключи по какой-то причине. Мы также можем установить состояние в значение false/null в setState() вместо использования replaceState().

Как можно следить за изменением состояния компонента?

При обновлении состояния вызывается метод жизненного цикла componentDidUpdate. Мы можем сравнить передаваемое состояние и пропы с предыдущими для выявления изменений:

componentDidUpdate(prevProps, prevState)

Обратите внимание: в предыдущих версиях React для обновления состояния можно было использовать componentWillUpdate(nextProps, nextState). Впоследствии данный метод был признан устаревшим.

Как рекомендуется удалять элемент из массива в состоянии компонента?

Оптимальный подход заключается в использовании метода Array.prototype.filter.

Создадим метод removeItem() для обновления состояния:

removeItem(index) {
this.setState({
data: this.state.data.filter((item, i) => i !== index)
})
}

Возможно ли использовать React без рендеринга HTML?

В новых версиях (>=16.2) это вполне возможно. Ниже представлено несколько вариантов:

render() {
return false
}
render() {
return null // рекомендуемый подход
}
render() {
return []
}
render() {
return <React.Fragment></React.Fragment>
}
render() {
return <></>
}

С undefined это не работает.

Как красиво отобразить JSON в React?

Мы можем использовать тег pre для сохранения форматирования, произведенного JSON.stringify():

const data = { name: 'Иван', age: 42 }

class User extends React.Component {
render() {
return (
<pre>
{JSON.stringify(data, null, 2)}
</pre>
)
}
}

React.render(<User />, document.getElementById('container'))

Почему нельзя обновлять пропы?

Философия React гласит, что пропы должны быть иммутабельными (неизменяемыми или неизменными) и однонаправленными (передаваемыми в одном направлении, сверху вниз, от предка к потомку). Это означает, что родительский компонент может передавать пропы дочерним, а последние не могут их модифицировать (для дочерних компонентов пропы доступны только для чтения).

Как установить фокус на инпут при загрузке страницы?

Это можно сделать, создав ссылку (ref) на элемент input и использовав метод componentDidMount():

class FocusableInput extends React.Component{
componentDidMount() {
this.nameInput.focus()
}

render() {
return (
<div>
<input defaultValue='Не в фокусе' />
<input
ref={(input) => (this.nameInput = input)}
defaultValue='В фокусе'
/>
</div>
)
}
}

Аналогичный функциональный компонент:

function FocusableInput() {
const inputRef = useRef()

useEffect(() => {
inputRef.current.focus()
}, [])

return (
<div>
<input defaultValue='Не в фокусе' />
<input
ref={inputRef}
defaultValue='В фокусе'
/>
</div>
)
}

Какие существуют способы обновления объекта состояния?

  1. Вызов setState() с объектом, объединяемым с состоянием.
  • Использование Object.assign() для создания копии объекта:
const user = Object.assign({}, this.state.user, { age: 42 })
this.setState({ user })
  • Использование spread-оператора:
const user = { ...this.state.user, age: 42 }
this.setState({ user })
  1. Вызов setState() с функцией:
this.setState(prevState => ({
user: {
...prevState.user,
age: 42
}
}))
  1. Вызов сеттера, возвращенного useState():
const [user, setUser] = useState({ name: 'Иван' })
setUser({ ...user, age: 42 })

Как получить версию React при запуске приложения в браузере?

Для этого можно использовать React.version:

const REACT_VERSION = React.version

ReactDOM.render(
<div>{`Версия React: ${REACT_VERSION}`}</div>,
document.getElementById('app')
)

Какие существуют подходы к добавлению полифилов в Create React App?

  1. Ручной импорт из core-js:

Создаем файл, например, polyfills.js и импортируем его в корневой файл index.js. Выполняем npm install core-js или yarn add core-js и импортируем необходимый функционал:

import 'core-js/fn/array/find'
import 'core-js/fn/array/includes'
import 'core-js/fn/number/is-nan'
  1. Использование специального сервиса:

Извлекаем из CDN polyfill.io определенные, специфичные для браузера полифилы посредством добавления такой строки в index.html:

<script src='https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes'></script>

В приведенном примере мы дополнительно запрашиваем Array.prototype.includes(), поскольку он не включен в стандартный набор.

Как использовать HTTPS вместо HTTP в Create React App?

Для этого следует использовать опцию HTTPS=true. Мы можем отредактировать раздел со скриптами в package.json следующим образом:

"scripts": {
"start": "set HTTPS=true && react-scripts start"
}

Или просто выполнить команду set HTTPS=true && npm start.

Как избежать использования относительных путей при импорте в Create React App?

Создаем файл .env в корневой директории проекта и добавляем в него адрес импорта:

NODE_PATH=src/app

Перезагружаем сервер для разработки. После этого у нас появится возможность импортировать все, что находится в src/app, без использования относительных путей.

Мы также можем создать в корневой директории проекта файл jsconfig.json следующего содержания:

{
"compilerOptions": {
"baseUrl": "src"
},
"include": [
"src"
]
}

В последнем случае импорт файлов будет начинаться с src:

// вместо `../../app`, например
import Component from 'app/Component.js'

Как добавить Google Analytics в React Router?

Добавьте обработчик к объекту history для записи каждого отображения страницы:

history.listen(function(location) {
window.ga('set', 'page', location.pathname + location.search)
window.ga('send', 'pageview', location.pathname + location.search)
})

Как обновлять состояние компонента каждую секунду?

Для этого нужно использовать setInterval(), изменяющий состояние. Не забудьте отключить таймер при размонтировании компонента во избежание ошибок и утечек памяти:

componentDidMount() {
this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000)
}

componentWillUnmount() {
clearInterval(this.interval)
}

Аналогичный функциональный компонент:

const [time, setTime] = useState(Date.now)

useEffect(() => {
const timerId = setTimeout(() => setTime(Date.now), 1000)
return () => {
clearInterval(timerId)
}
}, [time])

Как применить вендорные префиксы во встроенных стилях?

React не применяет вендорные префиксы автоматически. Нам необходимо добавлять их вручную:

<div style={{
transform: 'rotate(90deg)',
WebkitTransform: 'rotate(90deg)', // обратите внимание на большую букву `W`
msTransform: 'rotate(90deg)' // `ms` - это единственный вендорный префикс в нижнем регистре
}} />

Такие библиотеки, как styled-components добавляют префиксы автоматически.

Как экспортировать/импортировать компоненты с помощью React и ES6?

Для экспорта компонентов рекомендуется использовать экспорт по умолчанию:

import React from 'react'
import User from 'user'

export default class MyProfile extends React.Component {
render(){
return (
<User type="customer">
{/* ... */}
</User>
)
}
}

Вспомогательные элементы могут экспортироваться по названию.

Обратите внимание: при агрегации (повторном экспорте) экспортируемых компонентов в файле index.js, удобнее использовать именованный экспорт, в противном случае, придется делать так:

export { default as Component } from './Component'

Почему constructor() вызывается только один раз?

Алгоритм согласования (reconcilation) без дополнительной информации предполагает, что если определенный компонент находится на том же месте при повторном рендеринге, то это тот же самый компонент, что и раньше, поэтому используется предыдущий экземпляр вместо создания нового.

Как в React определяются константы?

Для определения константы можно использовать синтаксис статических полей класса:

class MyComponent extends React.Component {
static DEFAULT_PAGINATION = 10
}

Обратите внимание: данное предложение находится на 3 стадии рассмотрения.

Как программно вызвать возникновение события нажатия кнопки?

Мы можем использовать проп ref через колбек в качестве ссылки на HTMLInputElement, сохранить эту ссылку в свойстве класса и использовать ее для вызова события в обработчике с помощью метода click. Это делается в 2 этапа:

  1. Создаем ссылку в методе render:
<input ref={(input) => (this.inputElement = input)} />
  1. Вызываем событие клика в обработчике:
this.inputElement.click()

То же самое можно реализовать с помощью хука useRef.

Можно ли использовать async/await в React?

Новые версии React поддерживают синтаксис async/await из коробки. Раньше для этого надо было использовать Babel и плагин transform-async-to-generator.

Назовите общую структуру директорий

Существует два варианта структурирования файлов:

  1. Группировка по "фичам" или маршрутам (роутам):
common/
├─ Avatar.js
├─ Avatar.css
├─ APIUtils.js
└─ APIUtils.test.js
feed/
├─ index.js
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
└─ FeedAPI.js
profile/
├─ index.js
├─ Profile.js
├─ ProfileHeader.js
├─ ProfileHeader.css
└─ ProfileAPI.js
  1. Группировка по типам файлов:
api/
├─ APIUtils.js
├─ APIUtils.test.js
├─ ProfileAPI.js
└─ UserAPI.js
components/
├─ Avatar.js
├─ Avatar.css
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
├─ Profile.js
├─ ProfileHeader.js
└─ ProfileHeader.css

Назовите популярные библиотеки для работы с анимацией

React Motion и React Spring являются самыми популярными библиотеками для работы с анимацией в экосистеме React на сегодняшний день.

В чем заключаются преимущества использования модулей со стилями?

Рекомендуется избегать жесткого кодирования стилей в компонентах. Любые значения, которые предполагается использовать в нескольких компонентах, должны быть извлечены в собственные модули.

Например, эти стили могут быть извлечены в разные компоненты:

export const colors = {
white,
black,
blue
}

export const space = [
0,
8,
16
]

И затем импортироваться в компоненты по необходимости:

import { space, colors } from './styles'

Назовите популярные линтеры

Самым популярным линтером для JavaScript является ESLint. Для анализа специфического кода доступны специальные плагины. Одним из наиболее распространенных в случае с React является пакет eslint-plugin-react. По умолчанию он следует набору лучших практик, включая правила определения наличия ключей в циклах и полный набор типов пропов.

Другим популярным инструментом является eslint-plugin-jsx-a11y, который помогает исправлять распространенные проблемы с доступностью. Поскольку JSX отличается от обычного HTML, проблемы с текстом alt или tabindex, например, не будут обнаруживаться обычными плагинами.

Как выполнить AJAX-запрос и в каком методе жизненного цикла это следует делать?

Для этого можно использовать такие библиотеки, как jQuery AJAX, Axios или встроенный Fetch API. Данные должны запрашиваться в методе componentDidMount. Для обновления компонента (после получения данных) используется setState().

В следующем примере список сотрудников запрашивается из API и записывается в локальное состояние компонента:

class MyComponent extends React.Component {
constructor(props) {
super(props)

this.state = {
employees: [],
error: null
}
}

componentDidMount() {
fetch('https://api.example.com/items')
.then(res => res.json())
.then(
(data) => {
this.setState({
employees: data.employees
})
},
(error) => {
this.setState({ error })
}
)
}

render() {
const { error, employees } = this.state

if (error) {
return <div>Ошибка: {error.message}</div>
} else {
return (
<ul>
{employees.map((employee) => (
<li key={employee.name}>
{employee.name} - {employee.salary}
</li>
))}
</ul>
)
}
}
}

То же самое можно реализовать с помощью хука useEffect и сеттера из хука useState.

Что такое рендер-пропы?

Рендер-пропы (Render Props) - это техника распределения кода между компонентами с помощью пропа, чьим значением является функция. Следующий компонент использует рендер-проп, возвращающий React-элемент:

<DataProvider render={(data) => (
<h1>{`Привет, ${data.target}`}</h1>
)}/>

Этот паттерн широко используется такими библиотеками, как React Router (до 5 версии) и DownShift.

Маршрутизация в React

Что такое React Router?

React Router - это мощная библиотека маршрутизации, построенная на основе React, которая помогает добавлять новые страницы и перемещаться между ними очень быстро при сохранении синхронизации между адресом страницы и ее представлением.

Чем React Router отличается от библиотеки history?

React Router - это обертка библиотеки history, которая обеспечивает взаимодействие с браузерным window.history и хеш-историей. Она также позволяет сохранять историю в памяти при отсутствии в окружении глобальной истории, например, при разработке мобильных приложений (React Native) и юнит-тестировании с помощью Node.js.

Что такое Router?

React Router 4 предоставляет 3 компонента Router:

  1. BrowserRouter.
  2. HashRouter.
  3. MemoryRouter.

Указанные компоненты создают экземпляры browser, hash и memory истории, соответственно. React Router 4 делает свойства и методы экземпляра history связанными с мартшрутизатором приложения через контекст в объекте router.

Для чего предназначены методы push и replace?

Экземпляр истории имеет два метода для навигации:

  1. push.
  2. replace.

Если думать об истории как о массиве посещенных локаций, то метод push добавляет в массив новую локацию, а replace заменяет текущую локацию новой.

Как реализовать программную навигацию?

Существует 3 способа реализовать программную навигацию в компоненте:

  1. Использование функции высшего порядка withRouter:

withRouter() внедряет объект истории в качестве пропа в компонент. Этот объект предоставляет методы push и replace, позволяющие избежать использования контекста:

import { withRouter } from 'react-router-dom' // это также работает с 'react-router-native'

const Button = withRouter(({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Нажми на меня
</button>
))
  1. Использование компонента Route и рендер-пропов:

Компонент Route предоставляет такой же проп, что и withRouter(), так что доступ к методам истории можно также получать через проп history.

import { Route } from 'react-router-dom'

const Button = () => (
<Route render={({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Нажми на меня
</button>
)} />
)
  1. Использование контекста:

Данный подход использовать не рекомендуется, он считается нестабильным:

const Button = (props, context) => (
<button
type='button'
onClick={() => {
context.history.push('/new-location')
}}
>
Нажми на меня
</button>
)

Button.contextTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired
})
}

Как получить параметры строки запроса?

Возможность разбирать строку запроса была удалена из React Router 4, поскольку разработчики на протяжении нескольких лет просили добавить другую реализацию. Разработчикам была предоставлена возможность самостоятельного выбора реализации. Рекомендуемым подходом является использование библиотеки query-srting:

import queryString from 'query-string'

const parsed = queryString.parse(props.location.search)

Также можно использовать нативное решение - URLSearchParams():

const params = new URLSearchParams(props.location.search)

const name = params.get('name')

Когда можно получить предупреждение "Router может содержать только один дочерний компонент"?

Маршруты (роуты) должны быть обернуты в блок Switch (переключатель), поскольку этот блок является уникальным и рендерит только один компонент по определенному маршруту.

Прежде всего, необходимо импортировать Switch:

import { Switch, Router, Route } from 'react-router'

Затем определить в нем маршруты:

<Router>
<Switch>
<Route {/* ... */} />
<Route {/* ... */} />
</Switch>
</Router>

Как передать аргументы методу history.push?

В процессе навигации объекту history можно передавать некоторые пропы:

this.props.history.push({
pathname: '/template',
search: '?name=john',
state: { detail: response.data }
})

Свойство search, например, используется для передачи параметров строки запроса.

Как реализовать стандартную или NotFound-страницу?

Switch рендерит компонент по первому совпавшему маршруту. Route без пути всегда будет совпадать. Поэтому можно просто опустить атрибут path или же указать /* в качестве его значения:

<Switch>
<Route exact path="/" component={Home}/>
<Route path="/user" component={User}/>
<Route component={NotFound} />
</Switch>

Как получить объект истории?

Для получения объекта истории необходимо выполнить следующие шаги:

  1. Создать модуль, экспортирующий объект history, и импортировать этот модуль в проект.

Создаем файл history.js:

// history.js
import { createBrowserHistory } from 'history'

export default createBrowserHistory({
/* опциональный объект с настройками */
})
  1. Вместо встроенных роутеров (например, BrowserRouter) следует использовать компонент Router. Импортируем history в файл index.js:
// index.js
import { Router } from 'react-router-dom'
import history from './history'
import App from './App'

ReactDOM.render((
<Router history={history}>
<App />
</Router>
), holder)
  1. Можно использовать метод push объекта history по аналогии со встроенным объектом истории:
// some-other-file.js
import history from './history'

history.push('/go-here')

Как реализовать автоматическое перенаправление после выполнения пользователем входа в систему?

Пакет react-router предоставляет компонент Redirect. Рендеринг данного компонента приводит к перемещению в новую локацию. Как и в случае с перенаправлениями, выполняемыми сервером, новая локация перезапишет текущую в стеке истории:

import React, { Component } from 'react'
import { Redirect } from 'react-router'

export default class LoginComponent extends Component {
render() {
if (this.state.isLoggedIn === true) {
return <Redirect to="/your/redirect/page" />
} else {
return <div>Войдите, пожалуйста</div>
}
}
}

Интернационализация в React

Что такое React Intl?

React Intl - библиотека, облегчающая работу с интернационализацией в React. Она предоставляет готовые компоненты и API для обработки строк, чисел, дат и т.д. React Intl является адаптированной версией FormatJS.

Назовите основные возможности React Intl

React Intl предоставляет следующие возможности:

  1. Отображение чисел с разделителями.
  2. Правильное отображение даты и времени.
  3. Отображение разницы между датой и "сейчас".
  4. Плюрализация (перевод во множественное число) подписей в строках.
  5. Поддержка 150+ языков.
  6. Возможность запуска как в браузере, так и в Node.js.
  7. Следование стандартам.

Назовите основные способы форматирования данных в React Intl

Библиотека React Intl предоставляет 2 способа форматирования строк, чисел и дат:

  1. С помощью компонентов React:
<FormattedMessage
id='account'
defaultMessage='На балансе недостаточно средств.'
/>
  1. С помощью API:
const messages = defineMessages({
accountMessage: {
id: 'account',
defaultMessage: 'На балансе недостаточно средств.',
}
})

formatMessage(messages.accountMessage)

Как использовать FormattedMessage в качестве заменителя в React Intl?

Компонент Formatted из react-intl возвращает элементы, а не обычный текст, которые не могут использоваться в качестве заменителей (placeholders), альтернативного текста и т.д. Для этого необходимо использовать низкоуровневое API formatMessage(). Объект intl внедряется в компонент с помощью компонента высшего порядка injectIntl(), затем сообщение форматируется с помощью formatMessage(), доступного в этом объекте:

import React from 'react'
import { injectIntl, intlShape } from 'react-intl'

const MyComponent = ({ intl }) => {
const placeholder = intl.formatMessage({ id: 'messageId' })
return <input placeholder={placeholder} />
}

MyComponent.propTypes = {
intl: intlShape.isRequired
}

export default injectIntl(MyComponent)

Как получить текущую локализацию с помощью React Intl?

Текущую локализацию в любом компоненте приложения можно получить с помощью injectIntl():

import { injectIntl, intlShape } from 'react-intl'

const MyComponent = ({ intl }) => (
<div>{`Текущая локализация: ${intl.locale}`}</div>
)

MyComponent.propTypes = {
intl: intlShape.isRequired
}

export default injectIntl(MyComponent)

Как отформатировать дату с помощью React Intl?

Компонент высшего порядка injectIntl() предоставляет доступ к методу formatDate через пропы компонента. Этот метод используется внутренним механизмом экземпляров FormattedDate и возвращает строковое представление отформатированной даты:

import { injectIntl, intlShape } from 'react-intl'

const stringDate = this.props.intl.formatDate(date, {
year: 'numeric',
month: 'numeric',
day: 'numeric'
})

const MyComponent = ({ intl }) => (
<div>{`Отформатированная дата: ${stringDate}`}</div>
)

MyComponent.propTypes = {
intl: intlShape.isRequired
}

export default injectIntl(MyComponent)

Тестирование в React

Что такое поверхностный рендеринг в терминах тестирования?

Поверхностный рендеринг (shallow rendering) используется для юнит-тестирования в React. Он позволяет рендерить "плоский" компонент и оценивать результаты, возвращаемые его методом render, не заботясь о поведении дочерних компонентов, которые в момент тестирования еще не инстанцированы или не отрисованы.

Например, если у нас имеется такой компонент:

function MyComponent() {
return (
<div>
<span className='heading'>Заголовок</span>
<span className='description'>Описание</span>
</div>
)
}

Мы можем протестировать его следующим образом

import ShallowRenderer from 'react-test-renderer/shallow'

// код теста
const renderer = new ShallowRenderer()
renderer.render(<MyComponent />)

const result = renderer.getRenderOutput()

expect(result.type).toBe('div')
expect(result.props.children).toEqual([
<span className='heading'>Заголовок</span>,
<span className='description'>Описание</span>
])

Что такое TestRenderer?

Данный пакет предоставляет движок, который используется для рендеринга компонентов в виде "чистых" объектов JavaScript, не связанных с DOM или мобильным окружением. Он позволяет получить снимок иерархии компонентов (похожий на DOM-дерево), генерируемых ReactDOM или React Native без помощи браузера или jsdom:

import TestRenderer from 'react-test-renderer'

const Link = ({ page, children }) => <a href={page}>{children}</a>

const testRenderer = TestRenderer.create(
<Link page='https://www.facebook.com/'>Facebook</Link>
)

console.log(testRenderer.toJSON())
/*
{
type: 'a',
props: { href: 'https://www.facebook.com/' },
children: [ 'Facebook' ]
}
*/

Для чего предназначен ReactTestUtils?

ReactTestUtils, входящий в состав пакета with-addons, позволяет имитировать поведение DOM для целей юнит-тестирования.

Что такое Jest?

Jest - это JavaScript-фреймворк для юнит-тестирования, созданный Facebook на основе Jasmine, предоставляющий возможность автоматического создания "моков" (mocks) и браузерного окружения с помощью jsdom. Он часто используется для тестирования компонентов.

В чем заключаются преимущества Jest перед Jasmine?

Таких преимуществ несколько:

  • Автоматическое выполнение тестов из исходного кода.
  • Автоматическое внедрение зависимостей при выполнении тестов.
  • Возможность синхронного тестирования асинхронного кода.
  • Тесты запускаются в "фейковом" DOM (с помощью jsdom), что позволяет запускать тесты из командной строки.
  • Одновременный запуск нескольких тестов, что позволяет им выполняться быстрее.

Приведите пример использования Jest

Создадим тест для функции, возвращающей сумму двух чисел, находящейся в файле sum.js:

// sum.js
const sum = (a, b) => a + b

export default sum

Создаем файл sum.test.js, содержащий код теста:

import sum from './sum'

test('Сумма 1 и 2 равняется 3', () => {
expect(sum(1, 2)).toBe(3)
})

Затем добавляем в package.json следующую строку:

{
"scripts": {
"test": "jest"
}
}

Наконец, выполняем yarn test или npm test, и Jest выполняет тестирование:

yarn test
PASS ./sum.test.js
✓ Cумма 1 и 2 равняется 3 (2ms)

Управление состоянием в React

Обратите внимание: после появления в React хуков, в частности, useContext и useReducer, а также новых инструментов для управления состоянием типа Zustand, целесообразность использования Redux в React-приложениях вызывает большие сомнения. Однако следует иметь ввиду, что Redux используется в подавляющем большинстве существующих React-приложений, поэтому его изучение является полезным с точки зрения перспективы поддержки и развития таких проектов.

Что такое Flux?

Flux - это парадигма проектирования приложений, являющаяся альтернативой более традиционному паттерну MVC. Это не фреймворк или библиотека, а новый вид архитектуры, разработанный специально для React с учетом концепции однонаправленного потока данных. Facebook использует данный паттерн в своих внутренних проектах.

Рабочий процесс или взаимодействие компонентов диспетчера (dispatcher), хранилища (store) и представления (view) с учетом каналов ввода/вывода выглядит следующим образом:

flux

Что такое Redux?

Redux - это стабильный (предсказуемый) контейнер для хранения состояния JavaScript-приложений, основанный на паттерне проектирования Flux. Redux может использоваться с React и любой другой библиотекой. Он легкий (около 2 Кб) и не имеет зависимостей.

Назовите ключевые принципы Redux

Redux следует трем фундаментальным принципам:

  1. Единственный источник истины: состояние всего приложения хранится в одном древовидном объекте - хранилище (store). Единственное состояние-дерево облегчает наблюдение за изменениями, отладку и инспектирование кода.
  2. Состояние доступно только для чтения: единственный способ изменить состояние заключается в запуске операции (action) - объекте, описывающем произошедшее. Это позволяет гарантировать, что ни представления, ни сетевые запросы не будут изменять состояние напрямую.
  3. Изменение состояния производится с помощью "чистых" функций: для определения того, как изменяется состояние в зависимости от операции, создаются редукторы (reducers). Редукторы - это "чистые" функции, принимающие предыдущее состояние в качестве аргумента и возвращающие новое.

Проведите сравнение Redux и Flux

Отличия между Redux и Flux можно свести к следующему:

  1. Недопустимость мутаций: во Flux состояние может быть изменяемым, а Redux требует, чтобы состояние было иммутабельным, и многие библиотеки для Redux исходят из предположения, что мы никогда не будем менять состояние напрямую. Мы можем обеспечить иммутабельность состояния с помощью таких пакетов, как redux-immutable-state-invariant, immutable или просто условившись с другими членами команды о написании иммутабельного кода.
  2. Осторожность в выборе библиотек: Flux не пытается решать такие проблемы, как повторное выполнение/отмена выполнения, стабильность (постоянство) кода или проблемы, связанные с обработкой форм, явно, а Redux имеет возможность к расширению с помощью посредников (middlewares) и предохранителей (guards) хранилища, что породило вокруг него довольно богатую экосистему.
  3. Отсутствие интеграции с Flow: Flux позволяет осуществлять очень выразительную статическую проверку типов, а Redux пока не поддерживает такой возможности.

В чем разница между mapStateToProps() и mapDispatchToProps()?

mapStateToProps() - это утилита, позволяющая компонентам получать обновленное состояние (которое, например, было обновлено другим компонентом):

const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}

mapDispatchToProps() - утилита, позволяющая компонентам вызывать операции (которые могут приводить к обновлению состояния приложения):

const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}

Для mapDispatchToProps() рекомендуется всегда использовать короткую форму записи объекта. Redux оборачивает ее в другую функцию, которая выглядит как (…args) => dispatch(onTodoClick(…args)), и передает эту обертку в качестве пропа в компонент:

const mapDispatchToProps = ({
onTodoClick
})

Можно ли запускать операцию в редукторе?

Запуск операции (action) в редукторе (reducer) является антипаттерном. Редуктор не должен вызывать побочных эффектов, он должен принимать объект с типом операции (action.type) и полезной нагрузкой (action.payload) и возвращать объект с новым состоянием. Добавление обработчиков и запуск операций в редукторе могут привести к цепной реакции и другим негативным последствиям.

Как получить доступ к хранилищу за пределами компонента?

Для этого нужно экспортировать хранилище из модуля, в котором оно создано с помощью createStore(). Имейте ввиду, что оно не должно загрязнять глобальное пространство имен:

const store = createStore(myReducer)

export default store

Назовите недостатки паттерна MVW (Model-View-Whatever: MVC, MVP, MVVM и т.д.)

  1. Манипуляции с DOM с точки зрения производительности являются очень дорогостоящими, что делает приложение медленным и неэффективным.
  2. Для обеспечения возможности использования обратных зависимостей была разработана очень сложная схема на основе моделей и представлений.
  3. Происходит изменение большого количества данных в приложениях для совместной работы (таких как Google Docs).
  4. Не существует простого способа отменять действия без добавления большого количества дополнительного кода (boilerplate).

Существует ли что-либо общее между Redux и RxJS?

Названные библиотеки созданы для разных целей. Однако, между ними есть кое-что общее.

Redux - это инструмент для управления состоянием приложения. Как правило, он используется в качестве архитектурного решения для пользовательского интерфейса. Думайте о нем как об альтернативе (наполовину) Angular. RxJS - это библиотека для реактивного программирования (reactive programming). Обычно, она используется для выполнения асинхронных задач в JavaScript. Думайте о ней как об альтернативе промисам и async/await. Redux использует реактивную парадигму, поскольку хранилище состояния является реактивным. Хранилище наблюдает за операциями с расстояния и изменяет себя. RxJS следует той же парадигме, но вместо того, чтобы выступать в роли архитектуры, он предоставляет основные строительные блоки, наблюдаемые объекты (observables), для реализации указанного паттерна.

Как запустить операцию при монтировании компонента?

Запускать операции можно в методе componentDidMount, а проверять данные - в методе render:

class App extends Component {
componentDidMount() {
this.props.fetchData()
}

render() {
return this.props.isLoaded
? <div>Загружено</div>
: <div>Не загружено</div>
}
}

const mapStateToProps = (state) => ({
isLoaded: state.isLoaded
})

const mapDispatchToProps = { fetchData }

export default connect(mapStateToProps, mapDispatchToProps)(App)

Для чего используется connect() в Redux?

Для того, чтобы иметь возможность использовать хранилище в контейнере, необходимо выполнить следующие шаги:

  1. Использовать метод mapStateToProps: он передает части состояния из хранилища в определенные пропы.
  2. Подключить пропы к контейнеру: объект, возвращаемый mapStateToProps(), подключается к контейнеру с помощью connect() из react-redux:
import React from 'react'
import { connect } from 'react-redux'

class App extends React.Component {
render() {
return <div>{this.props.containerData}</div>
}
}

function mapStateToProps(state) {
return { containerData: state.data }
}

export default connect(mapStateToProps)(App)

Как сбросить состояние в Redux?

Для этого необходимо создать корневой редуктор (root reducer), делегирующий обработку операций редуктору, генерируемому методом combineReducers.

Создадим rootReducer, возвращающий начальное состояние после операции с типом USER_LOGOUT. Как мы знаем, редукторы возвращают начальное состояние при вызове с undefined в качестве первого аргумента, независимо от типа операции:

const appReducer = combineReducers({
/* редукторы уровня приложения */
})

const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
state = undefined
}

return appReducer(state, action)
}

При использовании пакета redux-persist может также потребоваться очистка хранилища. redux-persist сохраняет копию состояния в движке хранилища. Поэтому сначала нужно импортировать соответствующий движок, затем разобрать состояние перед установкой его значения в undefined и, наконец, очистить каждый ключ состояния хранилища:

const appReducer = combineReducers({
/* редукторы уровня приложения */
})

const rootReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
Object.keys(state).forEach(key => {
storage.removeItem(key)
})

state = undefined
}

return appReducer(state, action)
}

Для чего используется символ @ перед connect()?

Символ @ указывает на то, что мы имеем дело с декоратором JavaScript. Decorators делают возможным аннотирование и модификацию классов, их полей и методов во время определения класса.

Рассмотрим примеры настройки Redux без и с использованием декоратора:

  • Без декоратора:
import React from 'react'
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

function mapStateToProps(state) {
return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}

class MyApp extends React.Component {
// ...
}

export default connect(mapStateToProps, mapDispatchToProps)(MyApp)
  • C декоратором:
import React from 'react'
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

function mapStateToProps(state) {
return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}

@connect(mapStateToProps, mapDispatchToProps)
export default class MyApp extends React.Component {
// ...
}

Приведенные примеры почти идентичны, за исключением использования декоратора. Синтаксис декораторов пока не стандартизирован, является экспериментальным и может измениться в будущем (данное предложение находится на 3 стадии рассмотрения). Для поддержки декораторов можно использовать Babel.

В чем разница между контекстом React и Redux?

Мы можем использовать Context API напрямую, он отлично подходит для передачи данных глубоко вложенным компонентам - в этом состоит его основное назначение.

Redux - более мощный инструмент, предоставляющий большое количество возможностей, которыми не обладает интерфейс контекста. На самом деле, Redux использует контекст в своих внутренних механизмах, но не экстраполирует его в открытый интерфейс.

Почему функции, изменяющие состояние, в Redux называются редукторами?

Редукторы (reducers) возвращают аккумулированное состояние (основанное на всех предыдущих и текущей операциях) (см. метод Array.prototype.reduce). Они действуют подобно "редукторам состояния". При каждом вызове редуктора, ему в качестве аргументов передаются состояние и операция. Переданное состояние обновляется (аккумулируется с предыдущим) на основе операции, и возвращается новое состояние. Мы можем "редуцировать" несколько операций и начальное состояние (хранилища) и применять эти операции к состоянию для получения результирующего состояния.

Как выполнить AJAX-запрос в Redux?

Для этого можно использовать посредника (middleware) redux-thunk, позволяющего определять асинхронные операции.

Рассмотрим пример запроса аккаунта по id с помощью Fetch API:

export function fetchAccount(id) {
return dispatch => {
dispatch(setLoadingAccountState()) // показываем индикатор загрузки
fetch(`/account/${id}`, (response) => {
dispatch(doneFetchingAccount()) // скрываем индикатор загрузки
if (response