Вопросы по React. Версия 1
Ключевые концепции React
Что такое React
?
React
- это открытая (с открытым исходным кодом) JavaScript-библиотека для фронтенда, предназначенная для создания пользовательских интерфейсов, особенно, если речь идет о создании одностраничных приложений (Single Page Applications, SPA). Она отвечает за слой представления (view) в веб и мобильных приложениях. React
был разработан Jordan Walke - инженером Facebook
. React
был представлен на Facebook News Feed
в 2011 году, а для Instagram
- в 2012 году.
Назовите основные особенности React
Основными особенностями React
является следующее:
- Использование VirtualDOM (виртуальной объектной модели документа) вместо RealDOM (настоящий или реальный
DOM
), поскольку манипуляции с RealDOM являются дорогостоящими с точки зрения производительности. - Поддержка рендеринга на стороне сервера (Server Side Rendering, SSR).
- Следование принципу однонаправленного потока или связывания данных (one-directional data flow).
- Использование переиспользуемых (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 способа это сделать:
- Функциональные компоненты: это простейший способ создания компонента. Такие функции являются "чистыми" (pure), принимают объект с пропами в качестве аргумента и возвращают элемент (или несколько элементов во фрагменте):
function Greeting({ message }) {
return <h1>{`Привет, ${message}`}</h1>
}
- Классовые компоненты: для определения компонента также можно использовать
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>
)
}
}
Состояние похоже на проп, но оно является приватным (замкнуто в своей области видимости) и полностью контролируется компонентом, т.е. оно недоступно для других компонентов, кроме того, которому оно принадлежит и которое его определяет.
Что такое пропы?
Пропы (props) - это входные данные для компонента. Это простые значения (примитивы) или объект, содержащий несколько значений, которые передаются компонентам при их создании с помощью синтаксиса, похожего на атрибуты HTML-тегов
.
Основное назначение пропов в React
заключается в предоставлении компоненту следующего функционала:
- Передача данных компоненту.
- Вызов изменения состояния.
- Использование через
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
?
Вот некоторые из основных отличий:
- В
HTML
название события указывается строчными буквами (в нижнем регистре):
<button onclick="activateLasers()" />
В React
для этого используется "верблюжий стиль" (camelCase):
<button onClick={activateLasers}>
- В
HTML
можно вернутьfalse
для отключения поведения по умолчанию:
<a href='#' onclick="console.log('Ссылка!'); return false;" />
В React
необходимо явно вызывать метод preventDefault
:
function handleClick(event) {
event.preventDefault()
console.log('Ссылка!')
}
- В
HTML
необходимо вызывать функцию с помощью()
. ВReact
этого делать не нужно.
Как в колбеках JSX
определяется контекст методов или обработчиков событий?
Существует 3 способа это сделать:
- Привязка в конструкторе - в классах
JavaScript
методы не связаны с экземплярами по умолчанию. То же самое справедливо для обработчиков событий вReact
. Обычно, мы делаем привязку в конструкторе:
class Component extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// ...
}
}
- Синтаксис публичных полей класса - для привязки колбеков можно также использовать такой синтаксис:
handleClick = () => {
console.log('Это: ', this)
}
<button onClick={this.handleClick}>
Нажми на меня
</button>
- Стрелочные функции - также мы можем использовать стрелочные функции прямо в колбеках:
<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>
)
Обратите внимание:
- Использовать индексы в качестве ключей не рекомендуется, если порядок расположения элементов может измениться. Это может негативно сказаться на производительности, а также привести к проблемам с состоянием компонента.
- При извлечении элемента списка в качестве самостоятельного компонента применяйте ключи к этим компонентам, а не к тегу
li
. - При отсутствии пропа
key
в консоль будет выведено соответствующее предупреждение.
Для чего используются ссылки или рефы?
Реф (ref
) возвращает ссылку на DOM-элемент
. Этого в большинстве случаев следует избегать. Тем не менее, ссылки могут быть полезны при необходимости получения прямого доступа к DOM-элементу
или экземпляру компонента.
Как создаются ссылки?
Существует 2 подхода:
- Новый: ссылки создаются с помощью
React.createRef()
и привязываются к элементу с помощью атрибутаref
. Для того, чтобы иметь возможность использовать рефы во всем компоненте, просто присвойтеref
свойству экземпляра в конструкторе:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return <div ref={this.myRef} />
}
}
- Также можно использовать реф-колбеки (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 работает следующим образом:
- При любом изменении внутренних данных пользовательский интерфейс меняется в представлении виртуального DOM.
- Затем вычисляется разница между предыдущим и новым представлениями.
- После этого обновляются только те части реального DOM, которые подверглись изменениям.
В чем разница между теневым и виртуальным 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
стадий:
-
Монтирование: компонент готов к встраиванию в браузерный
DOM
. Эта стадия охватывает инициализацию вconstructor()
, а также методы жизненного циклаgetDerivedStateFromProps
,render
иcomponentDidMount
. -
Обновление: на данной стадии компонент обновляется либо из-за получения новых пропов, либо из-за обновления состояния с помощью
setState()
илиforceUpdate()
. Эта стадия охватывает такие методы жизненного цикла какgetDerivedStateFromProps
,shouldComponentUpdate
,render
,getSnapshotBeforeUpdate
иcomponentDidUpdate
. -
Размонтирование: на этой последней стадии компонент удаляется из браузерного
DOM
. Данная стадия включает метод жизненного циклаcomponentWillUnmount
.
Следует отметить, что в React
также имеются особые стадии применения изменений к DOM
:
-
Рендеринг: компонент рендерится без каких-либо побочных эффектов. Это применяется в отношении "чистых" компонентов. На данной стадии
React
может приостанавливать, прерывать или перезапускать рендеринг. -
Pre-commit: перед обновлением компонента существует момент, когда
React
читаетDOM
черезgetSnapshotBeforeUpdate()
. -
Commit:
React
изменяетDOM
и выполняет завершающие методы жизненного цикла, такие какcomponentDidMount
при монтировании,componentDidUpdate
при обновлении иcomponentWillUnmount
при размонтировании.
Стадии жизненного цикла в React 16.3+
(интерактивная версия)
До React 16.3
:
Назовите методы жизненного цикла
До 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
обычно используются для:
- Обеспечения возможности повторного использования кода, логики, а также для абстрагирования шаблонов.
- Отложенного рендеринга.
- Абстрагирования и манипуляции состоянием.
- Манипуляции пропами.
Как в 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
?
Вот некоторые причины:
- Фрагменты немного быстрее и используют меньше памяти. Реальная польза от этого ощущается в очень больших и глубоких деревьях элементов.
- Некоторые механизмы
CSS
, например,Flexbox
иGrid
используют связь предок-потомок, поэтому добавление дополнительныхdiv
может поломать макет страницы. - Меньшее количество элементов облегчает инспектирование
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
.
Набор предопределенных типов пропов:
PropTypes.number
.PropTypes.string
.PropTypes.array
.PropTypes.object
.PropTypes.func
.PropTypes.node
.PropTypes.element
.PropTypes.bool
.PropTypes.symbol
.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?
Вот список основных преимуществ:
- Повышение производительности приложения благодаря виртуальному DOM.
JSX
облегчает написание и чтение кода.- Возможность рендеринга как на стороне клиента, так и на стороне сервера.
- Возможность относительно простой интеграции с фреймворками, поскольку
React
- это всего лишь библиотека. - Возможность быстрого юнит и интеграционного тестирования с помощью таких инструментов, как
Jest
.
Какие ограничения имеются в React?
Кроме преимуществ, в React
существуют некоторые ограничения:
React
- это всего лишь библиотека, отвечающая за слой представления, а не полноценный фреймворк.- Его трудно изучать новичкам в веб-разработке (впрочем, как и любой другой фреймворк).
- Интеграция с традиционными
MVC-фреймворками
требует дополнительных настроек. - Код является более сложным из-за встроенных шаблонов и
JSX
. - Большое количество мелких компонентов приводит к сложности в проектировании и построении архитектуры приложения.
Что такое предохранители?
Предохранители (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
, которые могут быть использованы на верхнем уровне приложения. Большинству компонентов не нужен этот модуль. Вот некоторые из методов рассматриваемой библиотеки:
render
.hydrate
.unmountComponentAtNode
.findDOMNode
.createPortal
.
Для чего предназначен метод render
?
Данный метод используется для встраивания React-элемента
в DOM
в определенный контейнер и возврата ссылки на этот элемент. Если React-элемент
уже рендерился ранее, осуществляется обновление только той части DOM
, которая подверглась соответствующим изменениям.
ReactDOM.render(element, container[, callback])
При передаче render()
опционального колбека, он будет вызван после рендеринга или обновления компонента.
Что такое ReactDOMServer
?
ReactDOMServer
- это объект, позволяющий рендерить компоненты в виде статической разметки (обычно, используется на Node.js-серверах
). Данный объект в основном используется при рендеринге на стороне сервера. Следующие методы могут быть использованы как на сервере, так и в браузерном окружении:
renderToString
.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
имеет некоторые синтаксические отличия:
- Обработчики событий именуются в верблюжьем стиле (
onClick
), а не в нижнем регистре (onclick
). - В
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
:
- Поддержка синтаксиса
ES6
,React
,JSX
иFlow
. - Расширения языка после
ES6
, такие как spread- и rest-операторы. - Автоматическое добавление префиксов к стилям, поэтому нам не нужно вручную добавлять
-webkit-
и другие префиксы. - Быстрый запуск интерактивных юнит-тестов со встроенной поддержкой отчетов о покрытии кода.
- Автоматически перезапускаемый сервер для разработки, выводящий предупреждения о наиболее распространенных ошибках.
- Скрипт для сборки
JS
,CSS
и статических файлов для продакшна с хешами в названиях и картами ресурсов (source maps).
Назовите методы жизненного цикла компонента, относящиеся к монтированию
При создании и встраивании компонента в DOM
методы жизненного цикла вызываются в следующем порядке:
constructor
.static getDerivedStateFromProps
.render
.componentDidMount
.
Какие методы жизненного цикла были признаны устаревшими в React 16
?
Следующие методы жизненного цикла являются небезопасными практиками кодирования и усложняют работу с асинхронной логикой:
componentWillMount
.componentWillReceiveProps
.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 {
// ...
}
Какой порядок расположения методов в классовом компоненте является рекомендуемым?
Рекомендуемый порядок расположения методов от монтирования до рендеринга классового компонента следующий:
static
методы.constructor
.getChildContext
.componentWillMount
.componentDidMount
.componentWillReceiveProps
.shouldComponentUpdate
.componentWillUpdate
.componentDidUpdate
.componentWillUnmount
.- Обработчики событий, такие как
handleSubmit
илиhandleChange
. - Геттеры, необходимые для рендеринга, такие как
getSelectReason
илиgetFooterContent
. - Опциональные методы рендеринга, такие как
renderNavigation
илиrenderProfilePicture
. 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
:
onPointerDown
.onPointerMove
.onPointerUp
.onPointerCancel
.onGotPointerCapture
.onLostPointerCapture
.onPointerEnter
.onPointerLeave
.onPointerOver
.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>
)
}
Какие существуют способы обновления объекта состояния?
- Вызов
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 })
- Вызов
setState()
с функцией:
this.setState(prevState => ({
user: {
...prevState.user,
age: 42
}
}))
- Вызов сеттера, возвращенного
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
?
- Ручной импорт из
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'
- Использование специального сервиса:
Извлекаем из 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 этапа:
- Создаем ссылку в методе
render
:
<input ref={(input) => (this.inputElement = input)} />
- Вызываем событие клика в обработчике:
this.inputElement.click()
То же самое можно реализовать с помощью хука useRef
.
Можно ли использовать async/await
в React?
Новые версии React
поддерживают синтаксис async/await
из коробки. Раньше для этого надо было использовать Babel
и плагин transform-async-to-generator.
Назовите общую структуру директорий
Существует два варианта структурирования файлов:
- Группировка по "фичам" или маршрутам (роутам):
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
- Группировка по типам файлов:
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
:
BrowserRouter
.HashRouter
.MemoryRouter
.
Указанные компоненты создают экземпляры browser, hash и memory истории, соответственно. React Router 4
делает свойства и методы экземпляра history
связанными с мартшрутизатором приложения через контекст в объекте router
.
Для чего предназначены методы push
и replace
?
Экземпляр истории имеет два метода для навигации:
push
.replace
.
Если думать об истории как о массиве посещенных локаций, то метод push
добавляет в массив новую локацию, а replace
заменяет текущую локацию новой.
Как реализовать программную навигацию?
Существует 3 способа реализовать программную навигацию в компоненте:
- Использование функции высшего порядка
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>
))
- Использование компонента
Route
и рендер-пропов:
Компонент Route
предоставляет такой же проп, что и withRouter()
, так что доступ к методам истории можно также получать через проп history
.
import { Route } from 'react-router-dom'
const Button = () => (
<Route render={({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Нажми на меня
</button>
)} />
)
- Использование контекста:
Данный подход использовать не рекомендуется, он считается нестабильным:
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>
Как получить объект истории?
Для получения объекта истории необходимо выполнить следующие шаги:
- Создать модуль, экспортирующий объект
history
, и импортировать этот модуль в проект.
Создаем файл history.js
:
// history.js
import { createBrowserHistory } from 'history'
export default createBrowserHistory({
/* опциональный объект с настройками */
})
- Вместо встроенных роутеров (например,
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)
- Можно использовать метод
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
предоставляет следующие возможности:
- Отображение чисел с разделителями.
- Правильное отображение даты и времени.
- Отображение разницы между датой и "сейчас".
- Плюрализация (перевод во множественное число) подписей в строках.
- Поддержка 150+ языков.
- Возможность запуска как в браузере, так и в
Node.js
. - Следование стандартам.
Назовите основные способы форматирования данных в React Intl
Библиотека React Intl
предоставляет 2 способа форматирования строк, чисел и дат:
- С помощью компонентов
React
:
<FormattedMessage
id='account'
defaultMessage='На балансе недостаточно средств.'
/>
- С помощью
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
) с учетом каналов ввода/вывода выглядит следующим образом:
Что такое Redux
?
Redux
- это стабильный (предсказуемый) контейнер для хранения состояния JavaScript-приложений
, основанный на паттерне проектирования Flux
. Redux
может использоваться с React
и любой другой библиотекой. Он легкий (около 2 Кб) и не имеет зависимостей.
Назовите ключевые принципы Redux
Redux
следует трем фундаментальным принципам:
- Единственный источник истины: состояние всего приложения хранится в одном древовидном объекте - хранилище (
store
). Единственное состояние-дерево облегчает наблюдение за изменениями, отладку и инспектирование кода. - Состояние доступно только для чтения: единственный способ изменить состояние заключается в запуске операции (
action
) - объекте, описывающем произошедшее. Это позволяет гарантировать, что ни представления, ни сетевые запросы не будут изменять состояние напрямую. - Изменение состояния производится с помощью "чистых" функций: для определения того, как изменяется состояние в зависимости от операции, создаются редукторы (
reducers
). Редукторы - это "чистые" функции, принимающие предыдущее состояние в качестве аргумента и возвращающие новое.
Проведите сравнение Redux
и Flux
Отличия между Redux
и Flux
можно свести к следующему:
- Недопустимость мутаций: во
Flux
состояние может быть изменяемым, аRedux
требует, чтобы состояние было иммутабельным, и многие библиотеки дляRedux
исходят из предположения, что мы никогда не будем менять состояние напрямую. Мы можем обеспечить иммутабельность состояния с помощью таких пакетов, какredux-immutable-state-invariant
,immutable
или просто условившись с другими членами команды о написании иммутабельного кода. - Осторожность в выборе библиотек:
Flux
не пытается решать такие проблемы, как повторное выполнение/отмена выполнения, стабильность (постоянство) кода или проблемы, связанные с обработкой форм, явно, аRedux
имеет возможность к расширению с помощью посредников (middlewares
) и предохранителей (guards
) хранилища, что породило вокруг него довольно богатую экосистему. - Отсутствие интеграции с 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 и т.д.)
- Манипуляции с
DOM
с точки зрения производительности являются очень дорогостоящими, что делает приложение медленным и неэффективным. - Для обеспечения возможности использования обратных зависимостей была разработана очень сложная схема на основе моделей и представлений.
- Происходит изменение большого количества данных в приложениях для совместной работы (таких как
Google Docs
). - Не существует простого способа отменять действия без добавления большого количества дополнительного кода (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
?
Для того, чтобы иметь возможность использовать хранилище в контейнере, необходимо выполнить следующие шаги:
- Использовать метод
mapStateToProps
: он передает части состояния из хранилища в определенные пропы. - Подключить пропы к контейнеру: объект, возвращаемый
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.status === 200) {
dispatch(setAccount(response.json())) // обновляем состояние полученными данными
} else {
// ошибка
dispatch(response)
}
})
}
}
function setAccount(data) {
return { type: 'SET_ACCOUNT', data }
}
Обязательно ли хранить состояние всех компонентов в хранилище?
Данные приложения следует хранить в хранилище Redux
, а состояние компонентов пользовательского интерфейса в самих компонентах. У создателя Redux
Дэна Абрамова по этому поводу есть замечательная статья под названием "Следует ли вам использовать Redux?".
Как рекомендуется получать доступ к хранилищу?
Лучшим способом получить доступ к хранилищу Redux
в компоненте является использование функции connect
, которая создает новый компонент, оборачивающий существующий. Этот паттерн называется компоненты высшего порядка (HOC
), он является предпочтительным способом расширения функциональности компонента в React
. Это позволяет передавать в компонент состояние и "создателей операций" (action creators
), в том числе, при обновлении хранилища.
Создадим компонент FilterLink
с помощью connect()
:
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter
})
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(Link)
export default FilterLink
Поскольку такой вариант имеет несколько встроеных оптимизаций производительности и, как правило, меньше подвержен "багам", разработчики Redux
почти всегда рекомендуют использовать connect()
вместо прямого доступа к хранилищу (через контекст).
class MyComponent {
someMethod() {
doSomethingWith(this.context.store)
}
}
В чем разница между компонентом и контейнером?
Component - это классовый или функциональный компонент, описывающий визуальное представление определенной части приложения.
Container - это неофициальный термин для описания компонента, подключенного к хранилищу Redux
. Контейнеры "подписываются" на обновление состояния Redux
и "запускают" (dispatch
) операции. Такие компоненты, как правило, не рендерят DOM-элементы
: они делегируют эту обязанность дочерним компонентам, отвечающим за визуализацию.
Для чего в Redux
нужны константы?
Константы позволяют легко обнаруживать все случаи применения определенного типа операции в проекте. Они также позволяют избегать глупых ошибок, связанных с типами - немедленно выбрасывается исключение ReferenceError
.
Обычно, мы сохраняем константы в отдельном файле (constants.js
или actionTypes.js
).
export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const COMPLETE_ALL = 'COMPLETE_ALL'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
В Redux
мы используем их в 2 местах:
- При создании операции:
actions.js
:
import { ADD_TODO } from './actionTypes'
export function addTodo(text) {
return { type: ADD_TODO, text }
}
- В редукторе:
reducer.js
:
import { ADD_TODO } from './actionTypes'
export default (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
default:
return state
}
}
Какие существуют способы для привязки создателей операции к методу dispatch
в mapDispatchToProps()
?
Ниже представлено несколько возможных вариантов:
const mapDispatchToProps = (dispatch) => ({
action: () => dispatch(action())
})
const mapDispatchToProps = (dispatch) => ({
action: bindActionCreators(action, dispatch)
})
const mapDispatchToProps = { action }
Третий вариант является сокращением первого.
Для чего используется параметр ownProps
в методах mapStateToProps
и mapDispatchToProps
?
В ownProps
содержатся собственные пропы компонента, подключенного к Redux
с помощью функции connect
.
Если мы используем такой подключенный компонент:
import ConnectedComponent from './containers/ConnectedComponent'
<ConnectedComponent user='Иван' />
ownProps
внутри функций mapStateToProps
и mapDispatchToProps
будет таким объектом:
{ user: 'Иван' }
Мы можем использовать этот объект для определения значений, возвращаемых указанными функциями.
Как структурировать директории верхнего уровня в Redux
?
Большинство приложений имеют несколько "топовых" директорий:
- components: используется для "тупых" компонентов, не знающих о
Redux
. - containers: используется для "умных" компонентов, подключенных к
Redux
. - actions: используется для всех создателей операции - названия файлов указывают на соответствующие части приложения.
- reducers: используется для всех редукторов - названия файлов соответствуют ключам состояния.
- store: используется для инициализации хранилища.
Такая структура прекрасно подходит для небольших и средних приложений.
Что такое Redux Saga
?
redux-saga
- это библиотека, позволяющая легче и быстрее выполнять побочные эффекты (асинхронную логику вроде получения данных от сервера и доступа к кешу браузера) в React/Redux-приложениях
.
yarn add redux-saga
# или
npm i redux-saga
Определите ментальную модель Redux Saga
Redux Saga
- своего рода отдельный "тред" (поток выполнения кода) в приложении, отвечающий исключительно за побочные эффекты. redux-saga
- это middleware
для Redux
. Это означает, что данный тред может запускаться, приостанавливаться и отменяться из основного приложения с помощью обычных операций Redux
. Он имеет доступ к состоянию приложения и может выполнять запуск операций.
В чем разница между методами call
и put
в redux-saga
?
call
и put
являются функциями для создания эффектов. call
используется для создания эффекта описания, указывающего посреднику вызвать промис. put
создает эффект, указывающий посреднику запустить операцию.
Рассмотрим как эти эффекты работают применительно к запросу пользовательских данных:
// функция-генератор
function* fetchUserSaga(action) {
// функция `call` получает аргументы, которые передаются функции `api.fetchUser`,
// указываем `middleware` вызвать промис, разрешенное значение которого присваивается переменной `userData`
const userData = yield call(api.fetchUser, action.userId)
// указываем `middleware` запустить соответствующую операцию
yield put({
type: 'FETCH_USER_SUCCESS',
userData
})
}
Что такое Redux Thunk
?
Redux Thunk
- это посредник (middleware
), позволяющий создавать action creators
, возвращающих функции вместо операций. Redux Thunk
может использоваться для отложенного или условного запуска операций. Его внутренняя функция в качестве параметров принимает методы хранилища dispatch
и getState
.
В чем разница между redux-saga
и redux-thunk
?
Redux Thunk
и Redux Saga
предназначены для работы с побочными эффектами. В большинстве случаев Thunk
для этого пользуется промисами, а Saga
- генераторами. Thunk
проще в использовании и промисы лучше знакомы большинству разработчиков, Saga
предоставляет больше возможностей, но требуется хорошо разбираться в генераторах. Названные middlewares
могут использоваться совместно: можно начать с использования Thunk
и, при необходимости, перейти на Saga
.
Что такое Redux DevTools
?
Redux DevTools
- это Redux-окружение
для "путешествий во времени" и "живого" (live) редактирования кода с возможностью "горячей" (hot) перезагрузки, повторения операций и широко кастомизируемым интерфейсом. Для того, чтобы не возиться с самостоятельной установкой Redux DevTools
и его интеграцией в проект, можно воспользоваться соответствующими расширениями для Chrome
и Firefox
.
Назовите основные возможности Redux DevTools
Вот некоторые из основных возможностей Redux DevTools
:
- Возможность инспектирования каждого состояния и полезной нагрузки операции.
- Возможность возвращаться назад, "отменяя" выполнение операций.
- Повторное вычисление каждой "зафиксированной" операции при изменении кода редуктора.
- Возможность увидеть, в процессе выполнения какой операции редуктор выбросил исключение и в чем именно заключается ошибка.
- Возможность сохранения сессии отладки кода между перезагрузками страницы с помощью метода
persistState
.
Что такое селекторы и зачем их использовать?
Селекторы (selectors) - это функции, принимающие состояние Redux
в качестве аргумента и возвращающие некоторые данные для передачи компоненту.
Например, так можно извлечь данные пользователя из состояния:
const getUserData = state => state.user.data
Селекторы имеют 2 главных преимущества:
- Селектор может вычислять производные данные, позволяя
Redux
записывать в хранилище минимально возможное состояние. - Селектор не выполняет повторных вычислений до тех пор, пока не изменится хотя быодин из его аргументов.
Что такое Redux Form
?
Redux Form
работает с React
и Redux
, позволяя формам в React
хранить состояние в Redux
. Redux Form
может использоваться с обычными HTML5-инпутами
, а также с популярными UI-фреймворками
, такими как Material UI
и React Bootstrap
.
Назовите основные возможности, предоставляемые Redux Form
Некоторые из основных особенностей Redux Form
:
- Значения полей записываются в хранилище
Redux
. - Синхронная/асинхронная валидация полей и отправка формы.
- Форматирование, разбор и нормализация значений полей.
Как добавить несколько посредников в Redux
?
Для этого можно использовать метод applyMiddleware()
.
Пример добавления посредников redux-thunk
и logger
:
import { createStore, applyMiddleware } from 'redux'
const createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore)
Как установить начальное значение в Redux
?
Для этого необходимо передать начальное состояние в качестве второго аргумента в метод createStore()
:
const rootReducer = combineReducers({
todos,
visibilityFilter
})
const initialState = {
todos: [{ id: 123, name: 'test', completed: false }]
}
const store = createStore(
rootReducer,
initialState
)
Чем Relay
отличается от Redux
?
Relay
похож на Redux
тем, что оба используют единственное хранилище. Основное отличие состоит в том, что Relay
управляет состоянием через сервер, доступ к состоянию (чтение данных) и его изменение осуществляется через GraphQL-запросы
. Relay
кеширует данные в целях оптимизации их получения, запрашивая только изменившиеся данные и ничего более.
Что такое операция?
Операция (action) - это обычный JavaScript-объект
, содержащий данные приложения, которые отправляются в хранилище. Операции должны иметь свойство type
, указывающее какой тип операции необходимо выполнить. Операции также могут содержать полезную нагрузку (payload
) - данные для обновления состояния.
Вот как может выглядеть операция по добавлению новой задачи в список:
// здесь используется константа
{
type: ADD_TODO,
text: 'New todo'
}
React Native
В чем разница между React Native
и React
?
React
- это JavaScript-библиотека
для создания пользовательских интерфейсов и полноценных веб-приложений, работающая как в клиентском, так и в серверном окружении.
React Native
- это фреймворк для разработки мобильных приложений (iOS
, Android
и Windows
), позволяющий использовать React
для создания компонентов и сам использующий его под капотом.
Как тестируются приложения React Native
?
React Native
может тестироваться только в симуляторах мобильного окружения. Для запуска приложения можно использовать Expo. При синхронизации с помощью QR-кода
, телефон и компьютер должны быть подключены к одной сети.
Как реализовать логгирование?
Для этого можно использовать функции console.log
, console.warn
и т.д. Начиная с React Native 0.29
, для вывода сообщений в консоль также можно использовать следующие команды:
react-native log-ios
react-native log-android
Как производить отладку?
Для отладки приложений React Native
необходимо выполнить следующие шаги:
- Запускаем приложение в симуляторе
iOS
, например. - Нажимаем
Command/Ctrl + D
- открывается страница по адресуhttp://localhost:8081/debugger-ui
. - Разрешаем Pause On Caught Exceptions для улучшения опыта отладки.
- Наживаем
Command + Option + I
/Ctrl + Shift + I
илиF12
для того, чтобы открыть инструменты разработчикаChrome
. - Отлаживаем приложение.
Библиотеки для React
Что такое Reselect
и как она работает?
Reselect
- это библиотека селекторов (для Redux
), которая использует концепцию мемоизации. Изначально она создавалась для вычисления производных данных из состояния Redux-приложений
, но вполне может применяться и к другой архитектуре или библиотеке.
Reselect
сохраняет копию входных/выходных данных последнего вызова и производит повторные вычисления только при изменении этих данных. Если передаются одни и те же данные, Reselect
возвращает результат из кеша. Мемоизация и кеширование являются полностью настраиваемыми.
Что такое Flow
?
Flow
- это инструмент для статической проверки типов, специально разработанный для обнаружения ошибок в типах JavaScript
. Типы Flow
делают более мелкие различия, чем традиционная система типов. Например, Flow
, в отличие от большинства других систем типов, помогает перехватывать ошибки, связанные с null
.
В чем разница между Flow
и PropTypes
?
Flow
- это инструмент статического анализа кода, который использует надмножество языка, позволяющее добавлять аннотации типов для всего кода и осуществлять проверку во время компиляции.
PropTypes
- это инструмент базовой проверки типов, встроенный в React
. Он может проверять только типы пропов, переданных компоненту. Если нам нужна большая гибкость с точки зрения проверки типов, то лучшим выбором будет использование Flow/TypeScript
.
Как использовать Font Awesome
в React
?
Для того, чтобы включить Font Awesome
в React-проект
, необходимо выполнить следующие шаги:
- Устанавливаем
font-awesome
:
yarn add font-awesome
# или
npm i font-awesome
- Импортируем стили
font-awesome
в файлindex.js
:
import 'font-awesome/css/font-awesome.min.css'
- Добавляем класс
font-awesome
в атрибутclassName
:
render() {
return <div><i className={'fa fa-spinner'} /></div>
}
Что такое React DevTools
?
React DevTools
(инструменты разработчика React
) позволяют инспектировать дерево компонентов, включая их состояние и пропы. Они существуют как в виде расширения для браузера (Chrome
и Firefox
), так и в виде самостоятельного приложения (работают с другими окружениями, такими как Safari
, IE
и React Native
).
Почему React DevTools
не загружаются в Chrome
для локальных файлов?
Для того, чтобы React DevTools
отображались при открытии локального HTML-файла
в браузере (file://...
), в Расширениях Chrome необходимо выбрать Allow access to file URLs (разрешить доступ к путям файлов).
Как использовать Polymer в React?
Для того, чтобы использовать Polymer
в React
, необходимо сделать следующее:
- Создать элемент
Polymer
:
<!-- calender-element.html -->
<link rel='import' href='../../bower_components/polymer/polymer.html' />
<script>
Polymer({
is: 'calender-element',
ready: function() {
this.textContent = 'Это календарь'
}
})
</script>
- Создать компонент
Polymer
, импортировав его вindex.html
:
<!-- index.html -->
<link rel='import' href='./src/polymer-components/calender-element.html'>
- Использовать этот компонент в
JSX
:
import React from 'react'
class MyComponent extends React.Component {
render() {
return (
<calender-element />
)
}
}
export default MyComponent
В чем заключаются преимущества React
перед Vue
?
React
имеет следующие преимущества перед Vue
:
- Предоставляет большую гибкость при разработке больших приложений.
- Его легче тестировать.
- Подходит для разработки мобильных приложений.
- Доступно больше информации и готовых решений.
Обратите внимание: приведенные выше преимущества являются субъективными и сильно зависят от опыта разработки.
В чем разница между React
и Angular
?
Отличия между React
и Angular
в табличной форме:
React | Angular |
---|---|
React - это библиотека, которая имеет только слой представления | Angular - это фреймворк, в котором реализован весь функционал MVC |
React умеет выполнять рендеринг на стороне сервера | Angular раньше выполнял рендеринг только на стороне клиента, но, начиная с Angular 2 , он также умеет рендерить компоненты на стороне сервера |
React использует JSX , который выглядит как HTML в JS , что может сбивать с толку | Angular следует "шаблонному" подходу к HTML , что делает код более коротким и понятным |
React Native , позволяющий разрабатывать мобильные приложения, быстрее и стабильнее | Ionic , соответственно, менее стабильный и более медленный |
В React поток данных является однонаправленным, что существенно облегчает отладку | В Angular поток данных двунаправленный, данные тесно связывают дочерний и родительский компоненты, что часто затрудняет отладку |
Обратите внимание: приведенные отличия является субъективными и сильно зависят от опыта разработки.
Почему в DevTools
не отображается вкладка React
?
При загрузке страницы React DevTools
устанавливает глобальную переменную __REACT_DEVTOOLS_GLOBAL_HOOK__
, после чего React
активирует этот хук в процессе инициализации приложения и использует его для взаимодействия с инструментами разработчика. Если сайт не использует React
или React
больше не может взаимодействовать с DevTools
, соответствующая вкладка отображаться не будет.
Что такое Styled Components
?
styled-components
- это JavaScript-библиотека
для стилизации React-приложений
. Она исключает необходимость интеграции между стилями и компонентами и позволяет писать стили, дополняемые JavaScript
. На сегодняшний день это одно из самых популярных решений CSS-in-JS
.
Приведите пример использования Styled Components
Создаем силизованные компоненты Title
и Wrapper
:
import React from 'react'
import styled from 'styled-components'
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
const Wrapper = styled.section`
padding: 4em;
background: deepskyblue;
`
Переменные Title
и Wrapper
являются обычными компонентами, которые можно рендерить как любые другие компоненты:
<Wrapper>
<Title>Наш первый стилизованный компонент</Title>
</Wrapper>
Что такое Relay
?
Relay
- это фреймворк JavaScript
, предоставляющий слой данных и возможность клиент-серверной коммуникации для веб-приложений, в которых используется слой представления React
.
Как создать TypeScript-проект
с помощью Create React App
?
Начиная с react-scripts@2.1.0
, в create-react-app
встроена возможность автоматического создания React/TypeScript-проектов
. Для этого достаточно указать --template typescript
или просто --typescript
после названия приложения:
npx create-react-app my-app --template typescript
# или
yarn create react-app my-app --typescript
Разное
Назовите основные возможности библиотеки Reselect
Основные возможности этой библиотеки:
- Селекторы могут вычислять производные данные, позволяя
Redux
сохранять минимально возможное состояние. - Селекторы являются эффективными: они не выполняют повторных вычислений до тех пор, пока не изменится один из переданных им аргументов.
- Допустима композиция селекторов: они могут передаваться другим селекторам.
Приведите пример использования Reselect
Выполним некоторые вычисления, связанные с заказом товара, с помощью Reselect
:
import { createSelector } from 'reselect'
const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100)
)
export const totalSelector = createSelector(
subtotalSelector,
taxSelector,
(subtotal, tax) => ({ total: subtotal + tax })
)
const exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'яблоко', value: 1.20 },
{ name: 'апельсин', value: 0.95 },
]
}
}
console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState)) // 0.172
console.log(totalSelector(exampleState)) // { total: 2.322 }
Можно ли использовать статические объекты в классовых компонентах?
Нет, statics
работает только в React.createClass()
:
someComponent = React.createClass({
statics: {
someMethod: function() {
// ...
}
}
})
Однако, мы можем создавать статические элементы внутри классов с помощью синтаксиса статических полей класса:
class Component extends React.Component {
static propTypes = {
// ...
}
static someMethod() {
// ...
}
}
Или за пределами класса:
class Component extends React.Component {
// ...
}
Component.propTypes = {...}
Component.someMethod = function(){....}
Redux
может использоваться только с React
?
Redux
может использоваться в качестве хранилища данных для любого пользовательского интерфейса. Чаще всего, он используется совместно с React
и React Native
, но также имеется возможность его интеграции с Angular
, Angular 2
, Vue
, Mithril
и т.д. Redux
просто предоставляет механизм подписки (Pub/Sub
), который может использоваться в любом коде.
Требуются ли какие-либо дополнительные инструменты для работы с Redux
?
Redux
написан на синтаксисе ES6
и транспилируется в ES5
при сборке для продакшна с помощью Webpack
и Babel
. Нам не требуются какие-либо дополнительные инструменты. Redux
также предоставляется в виде UMD-модуля
, что позволяет использовать его напрямую, минуя стадию сборки.
Как обновить начальное состояние Redux Form
?
Для этого необходимо добавить настройку enableReinitialize : true
:
const InitializeFromStateForm = reduxForm({
form: 'initializeFromState',
enableReinitialize : true
})(UserEdit)
Обратите внимание: обновление начальных значений повлечет за собой обновление компонента формы.
Как с помощью PropTypes
разрешить использование разных типов одним пропом?
Для этого можно использовать метод oneOfType
.
Например, значение свойства height
может быть указано с помощью типа string
или number
:
Component.PropTypes = {
height: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}
Можно ли импортировать SVG-файл
как компонент React
?
Мы можем импортировать SVG
в компонент вместо его загрузки в виде файла. Такая возможность доступна, начиная с react-scripts@2.0.0
.
import { ReactComponent as Logo } from './logo.svg'
const App = () => (
<div>
{/* Logo - это обычный `React-компонент` */}
<Logo />
</div>
)
Обратите внимание: не забывайте использовать фигурные скобки при импорте.
Почему не рекомендуется использовать встроенные реф-колбеки или функции?
Если колбек ссылки определен как встроенная функция, он будет вызван дважды в процессе обновления, в первый раз с нулевым значением, второй - с DOM-элементом
. Это объясняется тем, что новый экземпляр функции создается при каждом рендеринге, поэтому React
необходимо удалить старую ссылку и установить новую:
class UserForm extends Component {
handleSubmit = () => {
console.log("Значением поля является: ", this.input.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={(input) => (this.input = input)} // это позволяет получить доступ к значению `input` в обработчике `submit`
/>
<button type='submit'>Отправить</button>
</form>
)
}
}
Однако, нам нужно, чтобы колбек вызывался только один раз при монтировании компонента. Простейшим решением является использование синтаксиса полей класса для определения функции:
class UserForm extends Component {
handleSubmit = () => {
console.log("Значением поля является: ", this.input.value)
}
setSearchInput = (input) => {
this.input = input
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={this.setSearchInput}
/>
<button type='submit'>Отправить</button>
</form>
)
}
}
Обратите внимание: при использовании хука useRef
в функциональных компонентах таких проблем не возникает.
Что такое перехват рендеринга?
Концепция перехвата рендеринга (render hijacking) - это возможность контролировать, что получит один компонент от другого. Это означает, что мы декорируем компонент, оборачивая его в HOC
(компонент высшего порядка). Это позволяет внедрять дополнительные пропы или осуществлять другие изменения, которые меняют логику рендеринга. В действительности, не происходит никакого перехвата, но использование HOC
заставляет компонент вести себя по-другому.
Как реализовать HOC-фабрику
?
Для реализации HOC
(компонента высшего порядка) в React
существует два способа.
- Проксирование пропов (Props Proxy - PP).
- Инверсия наследования (Inheritance Inversion - II).
Они используют разные подходы к управлению WrappedComponent
(обернутым компонентом).
Проксирование пропов
При использовании данного подхода метод render
возвращает React-элемент
с типом WrappedComponent
. Мы передаем ему пропы, полученные HOC
, поэтому данный подход называется Props Proxy:
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
Инверсия наследования
При использовании данного подхода возвращаемый HOC-класс
расширяет WrappedComponent
. Он называется Inheritance Inversion потому, что вместо того, чтобы WrappedComponent
расширял некоторый класс Enhancer
, он сам пассивно расширяется "усилителем" (enchancer - усилитель). Отношения между ними напоминают инверсию:
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
Как передать число в компонент?
Числа просто заключаются в фигурные скобки ({}
), а строки дополнительно закавычиваются:
React.render(<User age={30} department={"IT"} />, document.getElementById('container'))
Обязательно ли хранить все состояние в Redux
? Можно ли использовать внутреннее состояние компонентов?
Мы сами решаем, что использовать. В этом заключается одна из задач разработчика - определить, какое состояние требуется приложению, и где должна храниться каждая часть этого состояния. Одни разработчики предпочитают хранить все состояние в Redux
, что обеспечивает полную сериализацию и управляемость приложения. Другие предпочитают хранить некритичное состояние UI
, такое как "открыт ли выпадающий список или модальное окно" внутри соответствующего компонента.
Ниже представлены основные правила для определения того, какие типы данных следует хранить в Redux
:
- Нуждаются ли другие части приложения в этих данных?
- Требуется ли создавать производные данные на основе оригинальных?
- Используются ли эти данные несколькими компонентами?
- Существует ли вероятность того, что потребуется восстанавливать прошлое состояние?
- Собираетесь ли мы кешировать данные (для использования версии из кеша вместо выполнения повторного запроса)?
Для чего предназначен метод registerServiceWorker
?
React
создает сервис-воркера (service worker) без необходимости предварительной настройки из коробки (при использовании шаблона pwa
в create-react-app
). Сервис-воркер - это веб-API
, позволяющий записывать файлы приложения в кеш и возвращать их из него при отсутствии подключения к сети или медленном соединении, что сильно улучшает пользовательский опыт, обеспечивая возможность работы приложения в режиме офлайн. Сервис-воркер - это своего рода прокси для HTTP-запросов
.
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import registerServiceWorker from './registerServiceWorker'
ReactDOM.render(<App />, document.getElementById('root'))
registerServiceWorker()
Что такое React.memo()
?
Классовым компонентам можно запретить повторный рендеринг, если их пропы остались прежними, с помощью PureComponent
или shouldComponentUpdate()
. Теперь и у функциональных компонентов имеется такая возможность благодаря функции-обертке React.memo
:
const MyComponent = React.memo(function MyComponent(props) {
/* повторный рендеринг данного компонента выполняется только при изменении передаваемых ему пропов */
})
Похожий (но не аналогичный) функционал предоставляет хук useMemo
.
Что такое React.lazy()
?
Функция React.lazy
позволяет рендерить результаты динамического импорта как обычные компоненты. Она автоматически загружает "бандл", содержащий OtherComponent
(см. пример ниже), когда данный компонент будет отрендерен. Функция возвращает промис, который разрешается модулем с экспортом по умолчанию, содержащим React-компонент
. OtherComponent
обязательно должен быть обернут в компонент Suspense
:
import React, { Suspense } from 'react'
const OtherComponent = React.lazy(() => import('./OtherComponent'))
function MyComponent() {
return (
<Suspense>
<OtherComponent />
</Suspense>
)
}
Обратите внимание: React.lazy()
и Suspense
пока не доступны для рендеринга на стороне сервера. Если нам требуется разделение кода в приложении, которое рендерится на сервере, используйте библиотеку React Loadable
.
Как предотвратить лишние обновления компонента с помощью setState()
?
Мы можем сравнивать предыдущее состояния с текущим и решать, следует ли повторно рендерить компонент. Для предотвращения повторного рендеринга из setState()
достаточно вернуть null
.
Пример условного рендеринга профиля пользователя:
getUserProfile = (user) => {
const latestAddress = user.address
this.setState(state => {
if (state.address === latestAddress) {
return null
} else {
return { title: latestAddress }
}
})
}
Как рендерить числа, строки и массивы в React 16
?
Массивы: в отличие от предыдущих версий, метод render
не обязательно должен возвращать единственный элемент. Он вполне может возвращать несколько дочерних элементов без обертки.
Например, вот список/массив разработчиков:
const ReactJSDevs = () => {
return [
<li key="1">John</li>,
<li key="2">Jane</li>
]
}
Мы можем объединить этот массив с другим компонентом:
const JSDevs = () => {
return (
<ul>
<li>Bob</li>
<ReactJSDevs/>
<li>Alice</li>
</ul>
)
}
Строки и числа: мы также можем возвращать из метода render
строки и числа:
// строка
render() {
return 'Добро пожаловать в мир React!'
}
// число
render() {
return 2022
}
Как использовать синтаксис "определения полей класса" в классовых компонентах?
Синтаксис классовых компонентов может быть сильно сокращен с помощью "определения полей класса". Данный синтаксис позволяет инициализировать локальное состояние без конструктора и определять методы класса с помощью стрелочных функций без их привязки к экземпляру.
Рассмотрим пример счетчика:
class Counter extends Component {
state = { value: 0 }
handleIncrement = () => {
this.setState((prevState) => ({
value: prevState.value + 1
}))
}
handleDecrement = () => {
this.setState((prevState) => ({
value: prevState.value - 1
}))
}
render() {
return (
<div>
{this.state.value}
<button onClick={this.handleIncrement}>+</button>
<button onClick={this.handleDecrement}>-</button>
</div>
)
}
}
Что такое хуки?
Хуки (hooks) - это относительно новая возможность, представленная в React 16.8
, позволяющая использовать состояние и другие "реактивные" возможности в функциональных компонентах.
Пример использования хука useState
:
import { useState } from 'react'
function Example() {
// переменная `count` содержит начальное значение состояния компонента
// `setCount` - функция для обновления этого значения
// об этом можно думать, как о паре геттер/сеттер
const [count, setCount] = useState(0)
return (
<div>
<p>Вы кликнули {count} раз</p>
<button onClick={() => setCount(count + 1)}>
Нажми на меня
</button>
</div>
)
}
Назовите правила использования хуков
При использовании хуков необходимо соблюдать 2 правила:
- Хуки не должны вызываться внутри циклов, условий или вложенных функций. Это позволяет обеспечить одинаковый порядок вызова хуков при повторном рендеринге и сохранять состояние хуков между несколькими вызовами
useState()
иuseEffect()
. - Хуки можно вызывать только внутри функциональных компонентов
React
и других хуков, они не могут вызываться их внутри обычных функций.
Как обеспечить соблюдение правил использования хуков?
Команда React
разработала специальный ESLint-плагин
, который следит за соблюдением правил использования хуков. Этот плагин можно установить следующим образом:
yarn add eslint-plugin-react-hooks
# или
npm i eslint-plugin-react-hooks
И добавить в настройки ESLint
следующее:
// файл с настройками линтера, например, `.eslintrc.json`
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error"
}
}
Обратите внимание: данный плагин применяется по умолчанию при использовании Create React App
для создания проекта.
В чем разница между Flux
и Redux
?
Ниже представлены основные отличия между Flux
и Redux
:
Flux | Redux |
---|---|
Состояние мутабельно | Состояние иммутабельно |
Хранилище содержит как состояние, так и логику его изменения | Хранилище содержит только состояние |
Существует несколько хранилищ | Существует только одно хранилище |
Все хранилища являются самостоятельными и "плоскими" | Одно хранилище, включающее иерархию редукторов |
Имеется диспетчер-одиночка ("синглтон") | Концепция диспетчера как таковая отсутствует |
React-компоненты подписываются на хранилище | Компоненты-контейнеры используют функцию connect |
В чем заключаются преимущества использования React Router 4
?
Ниже представлены основные преимущества использования React Router 4
:
API
реализует компонентный подход. Роутер представлен в виде обычного компонента (BrowserRouter
, например), который оборачивает дочерние компоненты (Switch
,Route
и др.).- Нет необходимости работать с историей браузера напрямую. Роутер сам об этом позаботится - главное, не забыть обернуть маршруты в
Router
. - Размер приложения уменьшается за счет использования только определенных модулей (модули ядра, веб или нативные модули).
Опишите сигнатуру метода жизненного цикла componentDidCatch
Метод componentDidCatch
вызывается после того, как в любом из его потомков выбрасывается исключение. Он принимает два параметра:
error
- объект выброшенного исключения.info
- объект с ключомcomponentStack
, содержащим информацию о том, какой компонент выбросил исключение.
Сигнатура метода:
componentDidCatch(error, info)
В каких случаях предохранители не перехватывают ошибки?
Предохранители (error boundaries) не срабатывают в следующих случаях:
- Внутри обработчиков событий.
- В асинхронном коде, использующем колбеки
setTimeout
илиrequestAnimationFrame
. - В процессе рендеринга на стороне сервера.
- Когда исключение выбрасывается в самом предохранителе.
Почему в обработчиках событий предохранители не нужны?
Предохранители (error boundaries) не перехватывают ошибки внутри обработчиков событий. Обработчики не вызываются во время рендеринга, в отличие от метода render
или методов жизненного цикла. Поэтому React
знает, как справиться с ошибками в обработчиках. Для перехвата ошибок за пределами обработчика можно использовать блок try/catch
:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { error: null }
}
handleClick = () => {
try {
// ...
} catch (error) {
this.setState({ error })
}
}
render() {
if (this.state.error) {
return <h1>Ошибка: {this.state.error}</h1>
}
return <div onClick={this.handleClick}>Нажми на меня</div>
}
}
В чем разница между блоком try/catch
и предохранителями?
try/catch
предназначен для работы с императивным кодом, а предохранители (error boundaries) - с декларативным, результаты которого отображаются на экране.
Пример использования try/catch
:
try {
showButton()
} catch (error) {
// ...
}
Пример использования предохранителя:
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
Если где-то глубоко в дереве компонента возникнет ошибка, она будет обработана ближайшим предохранителем.
Что происходит с необработанными ошибками в React 16
?
В React 16
ошибки, которые не были перехвачены предохранителем, приведут к размонтированию всего дерева компонента. Причина такого решения состоит в том, что лучше полностью удалить интерфейс, чем отобразить его неправильную версию. Например, лучше ничего не отобразить, чем отобразить неправильную сумму в приложении для оплаты товаров.
Где следует размещать предохранители?
Это зависит от потребностей приложения. Можно использовать один из следующих подходов:
- Обернуть в предохранитель (error boundary) маршруты (роуты) верхнего уровня для отображения общего сообщения об ошибке для всего приложения.
- Оборачивать в предохранители отдельные компоненты во избежание "падения" всего приложения.
В чем заключается преимущество трассировки стека компонента перед предохранителями?
Кроме сообщений об ошибках и обычной трассировки стека, React
отображает трассировку стека компонента с названиями компонентов и пронумерованными строками с помощью предохранителей (error boundaries).
Пример отображения трассировки стека компонента BuggyCounter
:
Какой метод является обязательным для любого классового компонента?
Единственный обязательным методом любого классового компонента React
является render()
, все остальные методы являются опциональными.
Какие типы данных может возвращать метод render
?
Метод render
может возвращать следующие типы данных:
- React-элементы: элементы, преобразуемые в узлы
DOM
. Они включают в себяHTML-элементы
, такие как<div>
и пользовательские элементы. - Массивы и фрагменты: элементы, представляющие собой обертки для нескольких дочерних элементов.
- Порталы: позволяют рендерить дочерние элементы в другом поддереве
DOM
. - Строки и числа: встраиваются в
DOM
в виде текстовых узлов. - Логические значения и
null
: не отображаются на экране, используются для условного рендеринга. - Некоторые другие.
В чем заключается основное назначение конструктора классового компонента?
Конструктор предназначен для:
- Инициализации локального состояния компонента посредством присваивания
this.state
какого-либо объекта. - Привязки методов обработчиков событий к экземпляру компонента.
Пример обоих случаев:
constructor(props) {
super(props)
// вызывать `this.setState()` здесь нельзя
this.state = { counter: 0 }
this.handleClick = this.handleClick.bind(this)
}
Обязательно ли определять конструктор в компоненте?
Нет, это не обязательно. Если нам не нужно инициализировать состояние или определять контекст методов, тогда можно реализовать компонент без конструктора. Инициализировать состояние экземпляра также можно с помощью синтаксиса определения полей класса.
Что такое пропы по умолчанию?
Свойство defaultProps
определяет пропы, которые устанавливаются классу по умолчанию. Значения данного объекта используются для неопределенных пропов.
Создадим дефолтный проп для цвета компонента кнопки:
class MyButton extends React.Component {
// ...
}
MyButton.defaultProps = {
color: 'red'
}
Если при использовании компонента MyButton
, ему не будет передан проп color
, значением этого пропа станет значение по умолчанию, т.е. red
.
render() {
return <MyButton /> // значением `props.color` будет red
}
Обратите внимание: если мы передадим null
, значением пропа будет null
.
Почему не следует вызывать setState()
в componentWillUnmount()
?
setState()
не следует вызывать в componentWillUnmount()
, поскольку после размонтирования компонент больше не монтируется, а, значит, состояние компонента никогда не обновится.
Для чего используется static getDerivedStateFromError()
?
Метод getDerivedStateFromError
вызывается после того, как любой из дочерних компонентов выбросил исключение. Он получает ошибку в качестве аргумента и должен вернуть значение для обновления состояния.
Сигнатура этого метода выглядит так:
static getDerivedStateFromError(error)
Рассмотрим случай использования предохранителя:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// обновляем состояние для отображения резервного контента при следующем рендеринге
return { hasError: true }
}
render() {
if (this.state.hasError) {
// резервный контент
return <h1>Что-то пошло не так.</h1>
}
return this.props.children
}
}
В каком порядке вызываются методы жизненного цикла при повторном рендеринге компонента?
Обновление компонента может быть вызвано изменением пропов или состояния. При повторном рендеринге методы вызываются в следующим порядке:
static getDerivedStateFromProps
.shouldComponentUpdate
.render
.getSnapshotBeforeUpdate
.componentDidUpdate
.
Какие методы вызываются при обработке ошибок?
При возникновении ошибки в процессе рендеринга, в методе жизненного цикла, в конструкторе или любом потомке компонента, вызываются следующие методы:
static getDerivedStateFromError
.componentDidCatch
.
Для чего используется поле класса displayName
?
displayName
используется для отладки. Обычно, нам не нужно определять его явно, оно ссылается на название функции или класса, определяющего компонент. Явное обозначение может потребоваться для отображения другого названия в целях отладки или при создании компонента высшего порядка.
В следующем примере displayName
используется для указания на то, что мы имеем дело с результатом HOC
withSubscription
:
function withSubscription(WrappedComponent) {
class WithSubscription extends React.Component {/* ... */}
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`
return WithSubscription
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
Хорошо ли React-приложения
поддерживаются браузерами?
React
поддерживается всеми популярными браузерами, включая Internet Explorer 9
и выше. Для более старых браузеров требуются полифилы.
Для чего используется метод жизненного цикла unmountComponentAtNode
?
Метод unmountComponentAtNode
из пакета react-dom
используется для удаления смонтированного React-компонента
из DOM
, очистки его обработчиков и состояния. Если целевой компонент отсутствует, этот метод ничего не делает. Он возвращает true
при размонтировании компонента и false
в противном случае.
Рассматриваемый метод имеет следующую сигнатуру:
ReactDOM.unmountComponentAtNode(container)
Что такое разделение кода?
Разделение кода (code splitting) - это техника, используемая сборщиками модулей, такими как Webpack
и Browserify
, когда создается несколько "бандлов" (кусочков кода, соответствующих определенным частям приложения), подгружаемых по необходимости. React
поддерживает разделение кода с помощью динамического импорта.
В приведенном ниже примере moduleA.js
выделяется в отдельный "чанк" (chunk - часть, кусок), который загружается только после того, как пользователь нажал кнопку "Загрузить":
moduleA.js
:
const moduleA = 'Привет'
export { moduleA }
App.js
:
import React, { Component } from 'react'
class App extends Component {
handleClick = () => {
import('./moduleA')
.then(({ moduleA }) => {
// использование модуля
})
.catch((err) => {
// обработка ошибки
})
}
render() {
return (
<div>
<button onClick={this.handleClick}>Загрузить</button>
</div>
)
}
}
export default App
Данная техника также используется для отложенной ("ленивой") загрузки модулей с помощью функции React.lazy()
и компонента Suspense
.
В чем заключаются преимущества использования строгого режима?
StrictMode
может быть полезен в следующих случаях:
- Идентификация компонентов с небезопасными методами жизненного цикла.
- Вывод предупреждений об использовании устаревших встроенных ссылок (inline refs).
- Определение неожиданных побочных эффектов.
- Определение использования устаревшего интерфейса контекста.
- Вывод предупреждений об использовании устаревшего метода
findDOMNode
.
Что такое фрагменты с ключами?
Фрагменты, определяемые с помощью React.Fragment
, могут иметь ключи. Типичным примером такого использования является создание коллекции фрагментов:
function Glossary(props) {
return (
<dl>
{props.items.map((item) => (
// без `key` React выведет в консоль предупреждение
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
)
}
Обратите внимание: key
- это единственный атрибут, который в настоящее время можно добавлять фрагменту. Как обещают разработчики React
, в будущем появится возможность добавлять фрагментам другие атрибуты, такие как обработчики событий.
Все ли HTML-атрибуты
поддерживаются в React
?
Начиная с React 16
, полностью поддерживаются все стандартные и пользовательские DOM-атрибуты
. Поскольку в React-компонентах
используются как кастомные, так и связанные с DOM
пропы, в React
используется camelCase (верблюжий стиль), как и в DOM API
.
Примеры использования стандартных HTML-атрибутов
:
<div tabIndex="-1" /> // node.tabIndex
<div className="Button" /> // node.className
<input readOnly={true} /> // node.readOnly
Данные пропы работают аналогично соответствующим HTML-атрибутами
, за исключением некоторых особых случаев. Также поддерживаются все SVG-атрибуты
.
Какие ограничения имеют HOC
?
Компоненты высшего порядка, кроме очевидных преимуществ, имеют некоторые ограничения:
HOC
не рекомендуется использовать внутри методаrender
:
render() {
// при каждом рендеринге создается новая версия `EnhancedComponent`
// `EnhancedComponent1 !== EnhancedComponent2`
const EnhancedComponent = enhance(MyComponent)
// это приводит к тому, что внутреннее поддерево компонентов каждый раз демонтируется/монтируется
return <EnhancedComponent />
}
Приведенный код ухудшает производительность приложения за счет повторного монтирования компонента, что также приводит к потере состояния компонента и всех его потомков. В данном случае HOC
следует вынести за пределы компонента: это приведет к однократному монтированию компонента.
- Статические методы не копируются автоматически:
При применении HOC
к компоненту, новый компонент не будет иметь статических методов исходного:
// определяем статический метод
WrappedComponent.staticMethod = function() { /*...*/ }
// применяем `HOC`
const EnhancedComponent = enhance(WrappedComponent)
// "усиленный" компонент не имеет статического метода
console.log(typeof EnhancedComponent.staticMethod) // undefined
Эту проблему можно решить посредством копирования методов в контейнер перед его возращением:
function enhance(WrappedComponent) {
class Enhance extends React.Component { /*...*/ }
// необходимо точно знать, какие методы копировать
Enhance.staticMethod = WrappedComponent.staticMethod
return Enhance
}
- Рефы не передаются:
В случае с HOC
приходится передавать все пропы оборачиваемому компоненту, но это не работает со ссылками. Это объясняется тем, что ссылка - это не совсем обычный проп, такой как ключ (key), например. Для передачи рефов следует использовать React.forwardRef()
.
Как отлаживать forwardRefs
в DevTools
?
React.forwardRef()
принимает рендер-функцию в качестве параметра и DevTools
используют эту функцию для определения того, что следует отображать для компонента, передаваемого по ссылке.
Например, если мы не укажем имя функции рендеринга или не используем свойство displayName
, тогда в DevTools
функция отобразится как ForwardRef
:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />
})
Если же мы именуем рендер-функцию, тогда она отобразится как ForwardRef(myFunction)
:
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />
}
)
В качестве альтернативы можно установить свойство displayName
для функции forwardRef
:
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />
}
// определяем отображаемое название компонента
// например, `ForwardRef(logProps(MyComponent))`
const name = Component.displayName || Component.name
forwardRef.displayName = `logProps(${name})`
return React.forwardRef(forwardRef)
}
В каких случаях пропы компонента по умолчанию будут иметь значение true
?
Если мы не передаем значение для пропа, его значением становится true
. Такое поведение соответствует поведению HTML
.
Приведенные ниже выражения эквиваленты:
<MyInput autocomplete />
<MyInput autocomplete={true} />
Обратите внимание: данный подход использовать не рекомендуется, поскольку он может конфликтовать с сокращенной формой записи объектов в JavaScript
(например, { name }
является сокращением для { name: name }
).
Что такое Next.js
? Назовите основные возможности, которые он предоставляет
Next.js
- это популярный и легковесный фреймворк для статических приложений и приложений с серверным рендерингом, построенных с помощью React
. Он также из коробки предоставляет решения для стилизации и маршрутизации. Ниже представлены основные возможности данного фреймворка:
- Рендеринг на стороне сервера по умолчанию.
- Автоматическое разделение кода для ускорения загрузки страниц.
- Простая клиентская маршрутизация (основанная на страницах -
pages
). - Среда для разработки, основанная на
Webpack
, с поддержкой "горячей" перезагрузки модулей (Hot Module Replacement, HMR). - Возможность реализации сервера на
Express
или любом другом фреймворке дляNode.js
. - Возможность самостоятельной настройки
Babel
иWebpack
.
Как передать обработчик события компоненту?
Мы можем передавать обработчики событий и другие функции дочерним компонентам как пропы. Они затем могут быть использованы следующим образом:
<button onClick={this.handleClick} />
Является ли использование стрелочных функций в методе render
хорошей практикой?
Да, является. Часто, самым легким способом является передача аргументов в функцию обратного вызова. Но при таком подходе необходимо внимательно следить за производительностью:
class Foo extends Component {
handleClick() {
console.log('Случилось нажатие на кнопку')
}
render() {
return <button onClick={() => this.handleClick()}>Нажми на меня</button>
}
}
Обратите внимание: использование стрелочной функции в методе render
приводит к созданию функции при каждом рендеринге, что может повлечь за собой проблемы с производительностью.
Как предотвратить множественный вызов функции?
Если мы используем обработчик событий, такой как onClick()
или onScroll()
, и хотим предотвратить слишком ранний вызов этого обработчика, тогда мы можем ограничить количество вызовов колбека. Для этого можно применить одну из следующих техник:
- Throttling: за одну единицу времени можно выполнить только один вызов функции. Данную технику можно реализовать с помощью функции
_.throttle()
из библиотекиLodash
. - Debouncing: вызов функции выполняется только по истечении определенного времени. Данную технику можно реализовать с помощью функции
_.debounce()
изLodash
. - RequestAnimationFrame: вызовы функции основаны на
requestAnimationFrame()
. Данную технику можно реализовать с помощью функцииrafSchd
из пакетаraf-schd
.
Как JSX
предотвращает атаки, связанные с инъекцией вредоносного кода?
React DOM
"обезвреживает" все значение, содержащиеся в JSX
, перед их рендерингом. Это исключает возможность внедрения постороннего кода в приложение. Все значение конвертируются в строку перед отрисовкой.
Пример использования данных, введенных пользователем:
const name = response.potentiallyMaliciousInput // потенциально опасные данные
const element = <h1>{name}</h1>
Это также позволяет предотвратить межсайтовый скриптинг (XSS
).
Как обновить отрендеренный элемент?
Мы можем обновить UI
(представленный отрисованным элементом), передав методу ReactDOM.render
новый элемент.
Пример часов, которые обновляются каждую секунду посредством вызова метода render
:
function tick() {
const element = (
<div>
<h1>Привет, народ!</h1>
<h2>Сейчас {new Date().toLocaleTimeString()}.</h2>
</div>
)
ReactDOM.render(element, document.getElementById('root'))
}
setInterval(tick, 1000)
Почему пропы доступны только для чтения?
Компонент в виде функции или класса никогда не должен модифицировать переданные ему пропы:
function capital(amount, interest) {
return amount + interest
}
Приведенная функция называется "чистой" (pure), потому что она не изменяет передаваемые ей значения и всегда возвращает одинаковый результат для одних и тех же аргументов. React
следует концепции "Все компоненты должны действовать подобно чистым функциям по отношению к пропам".
Почему состояние обновляется путем объединения?
При вызове setState()
в компоненте React
объединяет переданный объект с текущим состоянием.
Здесь у нас имеется состояние в виде 2 массивов для постов и комментариев:
constructor(props) {
super(props)
this.state = {
posts: [],
comments: []
}
}
Мы можем обновлять эти массивы по отдельности:
componentDidMount() {
fetchPosts().then((response) => {
this.setState({
posts: response.posts
})
})
fetchComments().then(({ comments }) => {
this.setState({
comments
})
})
}
В приведенном примере this.setState({ comments })
обновляет только массив с комментариями, не затрагивая массив с постами.
Обратите внимание: при использовании хука useState
состояния не объединяются автоматически. При вызове setState()
состояния необходимо объединять вручную:
const [state, setState] = useState({
posts: [],
comments: []
})
useEffect(() => {
fetchPosts().then(({ posts }) => {
setState({
...state,
posts
})
})
}, [])
useEffect(() => {
fetchPosts().then(({ comments }) => {
setState({
...state,
comments
})
})
}, [])
Как передать аргумент в обработчик событий?
При выполнении перебора обычной практикой является передача дополнительного аргумента обработчику событий. Это может быть реализовано с помощью стрелочной функции или метода bind
.
Пример обновления таблицы пользователей:
<button onClick={(e) => this.updateUser(userId, e)}>Обновить данные пользователя</button>
<button onClick={this.updateUser.bind(this, userId)}>Обновить данные пользователя</button>
В обоих случаях "синтетический" (synthetic) аргумент передается на второй позиции. При использовании стрелочных функций его необходимо передавать явно, при использовании bind()
он передается автоматически.
Как предотвратить рендеринг компонента?
Это можно сделать, вернув null
из компонента. Так можно реализовать условный рендеринг компонента:
function Greeting(props) {
if (!props.loggedIn) {
return null
}
return (
<div className="greeting">
Добро пожаловать, {props.name}!
</div>
)
}
class User extends React.Component {
constructor(props) {
super(props)
this.state = {loggedIn: false, name: 'Иван'}
}
render() {
return (
<div>
{/* предотвращаем рендеринг компонента, если пользователь не выполнил вход в систему */}
<Greeting loggedIn={this.state.loggedIn} />
<UserDetails name={this.state.name}>
</div>
)
}
}
Назовите условия для безопасного использования индексов в качестве ключей
Для безопасного использования индексов в качестве ключей (keys) требуется соблюдение 3 условий:
- Список и его элементы являются статическими - они не вычисляются и не изменяются на протяжении всего жизненного цикла компонента.
- Элементы списка не имеют уникальных идентификаторов.
- Список не фильтруется, порядок его элементов не меняется.
Должны ли ключи быть уникальными в глобальном контексте?
Ключи (keys) должны быть уникальными среди соседних элементов, но не в глобальном контексте. Это означает, что одни и те же ключи можно использовать в разных массивах.
Пример использования одинаковых ключей в разных блоках:
function Book(props) {
const index = (
<ul>
{props.pages.map((page) =>
<li key={page.id}>
{page.title}
</li>
)}
</ul>
)
const content = props.pages.map((page) =>
<div key={page.id}>
<h3>{page.title}</h3>
<p>{page.content}</p>
<p>{page.pageNumber}</p>
</div>
)
return (
<div>
{index}
<hr />
{content}
</div>
)
}
Назовите популярные решения для работы с формами в React
Formik
- это один из наиболее популярных инструментов для работы с формами в React
. Эта библиотека предоставляет готовые решения для валидации, отслеживания заполненности полей и реализации отправки формы.
Возможности названной библиотеки можно разделить на следующие категории:
- Запись и получение значений из состояния формы.
- Валидация и генерация сообщений об ошибках.
- Обработка отправки формы.
Formik
часто используется для создания масштабируемых, высокопроизводительных обработчиков форм.
Другим популярным решением является библиотека React Hook Form
.
Почему нам не приходится работать с наследованием в React
?
В React
вместо наследования рекомендуется использовать композицию для обеспечения возможности как совместного использования кода несколькими компонентами, так и повторного использования самих компонентов. Композиция вместе с пропами предоставляют в наше распоряжение все необходимое для кастомизации внешнего вида и поведения компонента явным и безопасным способом.
Если требуется совместное использование функционала, не связанного с UI
, тогда соответствующий код следует выносить в отдельный модуль. Затем компоненты могут импортировать этот модуль и использовать функцию, объект или класс без необходимости их расширения.
Можно ли использовать веб-компоненты в React
?
Да, мы вполне можем использовать веб-компоненты в React-приложении
. Даже несмотря на то, что многим разработчикам не нравится такое сочетание, это может потребоваться при использовании сторонних библиотек компонентов пользовательского интерфейса, реализованных с помощью веб-компонентов.
Пример использования веб-компонента для выбора даты Vaadin
:
import React, { Component } from 'react'
import './App.css'
import '@vaadin/vaadin-date-picker'
class App extends Component {
render() {
return (
<div className="App">
<vaadin-date-picker label="Когда вы родились?"></vaadin-date-picker>
</div>
)
}
}
export default App
Что такое динамической импорт?
Динамический импорт (dynamic import) - это синтаксис, позволяющий реализовать разделение кода (code splitting), т.е. загрузку модулей по необходимости:
- Обычный импорт:
import { add } from './math'
console.log(add(10, 20))
- Динамический импорт:
import("./math").then((math) => {
console.log(math.add(10, 20))
})
Что такое загружаемые компоненты?
Если требуется разделение кода (code splitting) в приложении с серверным рендерингом, рекомендуемым способом является использование загружаемых (loadable) компонентов, поскольку React.lazy()
и Suspense
недоступны на стороне сервера. Loadable
позволяет рендерить результаты динамического импорта в виде обычных компонентов:
Пример:
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
После этого OtherComponent
будет загружаться как отдельный "бандл" (кусок кода, соответствующий определенной части приложения).
Что такое Suspense
?
Если модуль содержит динамический импорт (dynamic import), который не успел загрузиться к моменту рендеринга родительского компонента, требуется отобразить некоторый запасной контент, например, в виде индикатора загрузки. Это можно реализовать с помощью компонента Suspense
.
Пример:
const OtherComponent = React.lazy(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Загрузка...</div>}>
<OtherComponent />
</Suspense>
</div>
)
}
Suspense
оборачивает "ленивый" (загружаемый отложенно) компонент.
Что такое основанное на маршрутах разделение кода?
Одним из лучших мест для разделения кода (code splitting) являются маршруты (роуты, routes). Текущая страница полностью обновляется, так что пользователь не сможет взаимодействовать с элементами предыдущей и текущей страниц одновременно. Таким образом, пользовательский опыт не будет испорчен.
Пример разделения кода с помощью библиотеки react-router
и функции React.lazy
:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import React, { Suspense, lazy } from 'react'
const Home = lazy(() => import('./routes/Home'))
const About = lazy(() => import('./routes/About'))
const App = () => (
<Router>
<Suspense fallback={<div>Загрузка...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
)
В приведенном примере разделение кода происходит на уровне маршрутов.
Приведите пример использования контекста
Context API
спроектирован для распределения данных, которые можно назвать глобальными, между несколькими React-компонентами
, независимо от уровня вложенности этих компонентов.
Пример прямого доступа к пропу theme
для стилизации компонента кнопки:
// создаем контекст со значением `light` по умолчанию
const ThemeContext = React.createContext('light')
// создаем компонент `App`, передающий значение темы всем потомкам
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}
}
// промежуточным компонентам не требуется передавать проп `theme` дальше (ниже)
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
)
}
// получаем значение темы в компоненте кнопки
class ThemedButton extends React.Component {
static contextType = ThemeContext
render() {
return <Button theme={this.context} />
}
}
В функциональном компоненте значение контекста можно получить с помощью хука useContext
.
Для чего используется дефолтное значение контекста?
Аргумент defaultValue
используется в случаях, когда в дереве компонента не найден подходящий провайдер контекста (Provider
). Это может быть полезным для тестирования компонентов в изоляции без необходимости их оборачивания в Provider
:
const MyContext = React.createContext(defaultValue)
Как использовать contextType
?
contextType
используется для потребления (consume) объекта контекста. Данное свойство может использоваться 2 способами:
- Свойство класса:
Свойству contextType
класса можно присвоить объект контекста, созданный с помощью метода React.createContext
. После этого можно потреблять ближайшее значение контекста посредством this.context
в любом методе жизненного цикла, включая метод render
:
class MyClass extends React.Component {
componentDidMount() {
let value = this.context
/* выполняем побочные эффекты при монтировании, используя значение `MyContext` */
}
componentDidUpdate() {
let value = this.context
/* ... */
}
componentWillUnmount() {
let value = this.context
/* ... */
}
render() {
let value = this.context
/* выполняем рендеринг, используя значение `MyContext` */
}
}
MyClass.contextType = MyContext
- Статическое поле:
contextType
можно инициализировать c помощью синтаксиса статических полей класса:
class MyClass extends React.Component {
static contextType = MyContext
render() {
let value = this.context
/* выполняем рендеринг, используя значение `MyContext` */
}
}
Что такое потребитель?
Потребитель (consumer) - это компонент, подписанный (реагирующий) на изменения контекста. Ему требуется функция в качестве дочернего элемента, получающая текущее значение контекста и возвращающая узел React
. Аргумент value
равняется пропу value
ближайшего провайдера данного контекста:
<MyContext.Consumer>
{(value) => /* выполняем рендеринг на основе значения контекста */}
</MyContext.Consumer>
Как решать проблемы производительности при использовании контекста?
Контекст использует идентификацию ссылок для определения необходимости в повторном рендеринге. Существуют некоторые ошибки, которые могу привести к непреднамеренному рендерингу в потребителях при повторном рендеринге родительского компонента.
Например, в представленном ниже примере все потребители будут перерисовываться при каждом рендеринге провайдера, поскольку каждый раз создается новый объект-значение пропа value
:
class App extends React.Component {
render() {
return (
<Provider value={{ something: 'нечто' }}>
<Toolbar />
</Provider>
)
}
}
Данная проблема может быть решена путем сохранения состояния в конструкторе родительского компонента:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: { something: 'нечто' },
}
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
)
}
}
Для чего используются forwardRefs
в HOC
?
Ссылки не могут передаваться потомкам, поскольку они не являются обычными пропами. Они обрабатываются React
особым образом, также как ключи (keys). При добавилении ссылки к HOC
, она будет указывать на самый внешний компонент, а не на оборачивающий. В этом случае можно использовать технику под названием "перенаправление или передача ссылки" (ref forwarding).
С помощью React.forwardRef
мы можем передать ссылку внутреннему компоненту:
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('старые пропы:', prevProps)
console.log('новые пропы:', this.props)
}
render() {
const { forwardedRef, ...rest } = this.props
// присваиваем кастомный проп `forwardedRef` в качестве ссылки
return <Component ref={forwardedRef} {...rest} />
}
}
return React.forwardRef((props, ref) => {
// передаем ссылку
return <LogProps {...props} forwardedRef={ref} />
})
}
Используем данный HOC
для вывода в консоль всех пропов компонента FancyButton
:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
export default logProps(FancyButton)
Теперь создадим ссылку и передадим ее компоненту. Это позволит установить фокус на кнопку:
import FancyButton from './FancyButton'
const ref = React.createRef()
ref.current.focus()
<FancyButton
label="Нажми на меня"
handleClick={this.handleClick}
ref={ref}
/>
Почему следует проявлять осторожность при использовании forwardRefs
в библиотеке компонентов?
Когда мы начинаем использовать forwardRef
в библиотеке компонентов, это чаще всего означает несовместимые изменения и релиз новой мажорной версии. Это объясняется изменением поведения библиотеки за счет присвоения ссылок и экспортируемых типов. Такие изменения могут сломать приложение и другие библиотеки.
Как создать классовый компонент без использования синтаксиса ES6
?
Это можно сделать с помощью модуля create-react-class
. Для пропов по умолчанию необходимо определить функцию getDefaultProps
в передаваемом объекте. Также требуется реализовать отдельный метод getInitialState
, возвращающий начальное значение:
var Greeting = createReactClass({
getDefaultProps: function() {
return {
name: 'Иван'
}
},
getInitialState: function() {
return { message: this.props.message }
},
handleClick: function() {
console.log(this.state.message)
},
render: function() {
return <h1>Привет, {this.props.name}!</h1>
}
})
Обратите внимание: при использовании createReactClass()
для всех методов доступно автоматическое связывание, т.е. нам не нужно использовать bind(this)
в конструкторе для обработчиков событий.
Можно ли использовать React
без JSX
?
Да, JSX
не является обязательным условием использования React
. На самом деле JSX
используется для того, чтобы избежать настройки компиляции в среде разработки. Каждый JSX-элемент
- всего лишь синтаксический сахар для React.createElement(component, props, ...children)
.
Пример с JSX
:
class Greeting extends React.Component {
render() {
return <div>Привет, {this.props.message}!</div>
}
}
ReactDOM.render(
<Greeting message="народ" />,
document.getElementById('root')
)
То же самое без JSX
:
class Greeting extends React.Component {
render() {
return React.createElement('div', null, `Привет, ${this.props.message}!`)
}
}
ReactDOM.render(
React.createElement(Greeting, { message: 'народ' }, null),
document.getElementById('root')
)
Что такое алгоритм определения различий?
React
нуждается в использовании алгоритма определения эффективного обновления UI
для совпадения с текущим состоянием дерева компонентов. Алгоритм определения различий требует выполнения минимального количества операций для преобразования одного дерева в другое. Тем не менее, сложность данного алгоритма составляет порядка O(n3)
, где n
- это количество элементов в дереве.
В этом случае для отображения 1000
элементов потребуется около миллиарда сравнений. Это очень много. Вместо этого, React
реализует эвристический алгоритм со сложностью O(n)
, основываясь на 2 важных предположениях:
- 2 элемента разных типов приводят к возникновению разных деревьев.
- Разработчик может отметить стабильные элементы с помощью пропа
key
.
Каким правилам следует алгоритм определения различий?
При сравнении 2 деревьев React
начинает с сравнения 2 корневых элементов каждого поддерева. Поведение различается в зависимости от типов этих элементов. Алгоритм согласования следует таким правилам:
- Элементы разных типов: если корневые элементы имеют разные типы,
React
уничтожает старое дерево и строит новое с нуля. Например, изменение элемента с<a>
на<img>
или с<Article>
на<Comment>
разных типов приводит к полной перестройке дерева компонентов. DOM-элементы
одного типа: при сравнении двухDOM-элементов
одинакового типа,React
"смотрит" на атрибуты обоих, сохраняет нижележащиеDOM-узлы
, и обновляет только изменившиеся атрибуты. Вот пример изменения значения атрибутаclassName
:
<div className="show" title="React" />
<div className="hide" title="React" />
- Компоненты одного типа: при обновлении компонента экземпляр остается прежним, поэтому состояние сохраняется между рендерингами.
React
обновляет пропы нижележащего экземпляра компонента для совпадения с новым элементом и вызывает методыcomponentWillReceiveProps
иcomponentWillUpdate
экземпляра. После этого вызывается методrender
и алгоритм определения различий рекурсивно сравнивает предыдущий результат с текущим. - Рекурсивное сравнение потомков: при рекурсивном сравнении потомков узла
DOM
,React
просто одновременно перебирает оба списка и отмечает различия. Например, добавление нового элемента в конец списка обрабатывается быстро, а в середину медленно, поскольку все элементы после нового будут помечены как новые и перерисованы.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
- Обработка ключей:
React
поддерживает атрибутkey
. Когда потомки имеют ключи,React
использует их для сравнения потомков оригинального дерева с потомками нового дерева. Поэтому использование ключей существенно повышает эффективность согласования деревьев:
<ul>
<li key="2015">John</li>
<li key="2016">Jane</li>
</ul>
<ul>
<li key="2014">Bob</li>
<li key="2015">John</li>
<li key="2016">Jane</li>
</ul>
Когда может потребоваться использовать ссылки?
Ссылки (рефы - refs) используются в следующих случаях:
- Управление фокусом, выделением текста или воспроизведением медиа.
- Запуск анимации.
- Интеграция со сторонними библиотеками для работы с
DOM
.
Обязательно ли проп должен называться render
при использовании рендер-пропов?
Несмотря на название паттерна, использовать render
в качестве названия пропа необязательно. Любой проп, который является функцией, указывающей компоненту, что следует рендерить, технически является "рендер-пропом".
Пример использования children
как пропа для рендера:
<Mouse children={(mouse) => (
<p>Позиция курсора мыши: {mouse.x}, {mouse.y}</p>
)}/>
На самом деле, проп children
не нуждается в имени в списке "атрибутов" JSX-элемента
. Его можно поместить за пределами элемента:
<Mouse>
{(mouse) => (
<p>Позиция курсора мыши: {mouse.x}, {mouse.y}</p>
)}
</Mouse>
При использовании данной техники (без названия), необходимо явно сообщить propTypes
о том, что состояние этого потомка должно быть функцией:
Mouse.propTypes = {
children: PropTypes.func.isRequired
}
В чем заключается проблема использования рендер-пропов в "чистых" компонентах?
Создание функции внутри метода render
противоречит назначению "чистого" компонента. Поскольку поверхностное сравнение пропов будет всегда возвращать false
для новых пропов, каждый рендеринг будет генерировать новое значение для рендер-пропа. Эту проблему можно решить путем определения функции рендеринга в качестве метода экземпляра.
Как создать HOC с помощью рендер-пропов?
Мы можем реализовать компонент высшего порядка (HOC
) с помощью обычного компонента с рендер-пропом. Например, если мы хотим получить HOC
withMouse
вместо компонента Mouse
, то можем создать его с помощью Mouse
с пропом для рендерига:
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
)
}
}
}
В этом случае рендер-пропы имеют возможность использовать другие паттерны.
Что такое Windowing
?
Windowing
- это техника, позволяющая рендерить небольшой набор элементов в один момент времени, что может существенно уменьшить время повторного рендеринга компонента, а также число создаваемых DOM-узлов
. Если приложение рендерит длинный список данных, то рекомендуется использовать эту технику. Популярными решениями являются react-window
и react-virtualized
. Они предоставляют несколько переиспользуемых компонентов для работы со списками, "гридами" (grids) и табличными данными.
Как отображать ложные значения в JSX
?
Ложные значения, такие как false, null, undefined
, как и true
являются валидными React-элементами
, но они ничего не рендерят. Для их отображения требуется предварительная конвертация (преобразование) в строку.
Пример:
<div>
Моя JavaScript-переменная - это {String(myVariable)}.
</div>
Назовите типичные случаи использования порталов
Порталы в React
используются в случае переполнения (overflow) родительского компонента: скрытые элементы или элементы, которые "вырваны" из контекста стека (стили z-index, position, opacity
и т.д.). Данная техника используется для визуализации "независимости" таких элементов от родительских контейнеров.
Примерами подобных элементов могут служить диалоговые или модальные окна, глобальные уведомления, всплывающие подсказки и т.д.
Как установить значение по умолчанию для неуправляемого компонента?
В React
значение атрибута элемента формы перезаписывает соответствующее значение в DOM
. В случае использования неуправляемых компонентов может потребоваться установить начальное значение, но оставить его неуправляемым при последующих обновлениях. Для решения этой задачи используется атрибут defaultValue
вместо атрибута value
:
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Имя пользователя:
<input
defaultValue="Иван"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Отправить" />
</form>
)
}
То же самое справедливо в отношении полей select
и textarea
. Однако, для полей checkbox
и radio
следует использовать атрибут defaultChecked
.
Назовите ваш любимый стек для разработки приложений на React
Несмотря на то, что технический стек различается от разработчика к разработчику, существуют некоторые популярные решения, которые используются повсеместно. Они включают в себя redux
, redux-thunk
и redux-saga
для управления состоянием и работы с асинхронным кодом, react-router
для маршрутизации, styled-components
для стилизации, axios
или fetch
для работы с REST API
и другие инструменты, такие как Webpack
, Babel
, reselect
и т.д.
В чем разница между настоящим и виртуальным DOM
?
Ниже представлены основные отличие между реальным и виртуальным DOM
:
Реальный DOM | Виртуальный DOM |
---|---|
Обновления медленные | Обновления быстрые |
Манипуляции с DOM очень дорогостоящие | Манипуляции с DOM не очень дорогие |
HTML можно обновлять напрямую | HTML нельзя обновлять напрямую |
Активная работа с DOM часто приводит к утечкам памяти | Утечки памяти практически полностью исключены |
При обновлении элемента создается новая DOM | При изменении элемента обновляется только JSX |
Как добавить Bootstrap
в React
?
Bootstrap
может быть добавлен в React-приложение
3 способами:
- С помощью
Bootstrap CDN
: это самый простой способ - добавляем соответствующие теги (стили и скрипт) вhead
. Bootstrap
как зависимость: при использовании сборщика модулей, такого какWebpack
, предпочтительной является установкаBootstrap
в качестве зависимости:
yarn add bootstrap
# или
npm i bootstrap
- Пакет
React Bootstrap
: в этом случае можно использовать "компонентную" версиюBootstrap
. Существует 2 популярных решения:react-bootstrap
reactstrap
Назовите популярные сайты или приложения, использующие React
в качестве фреймворка для фронтенда
Ниже представлен топ-10 сайтов, использующих React
как библиотеку для фронтенда:
- Uber
- Khan Academy
- Airbnb
- Dropbox
- Netflix
- PayPal
Рекомендуется ли использовать технику CSS-in-JS
в React
?
React
не предоставляет инструмента для работы со стилями. Для новичка хорошей отправной точкой может стать определение стилей в отдельном CSS-файле
и ссылка на него через атрибуты className
. Функционал по использованию техники CSS-in-JS
предоставляется сторонними библиотеками, такими как styled-components
.
Как запрашивать данные с помощью хуков?
Хук, отвечающий за побочные эффекты, называется useEffect
. Именно этот хук используется для запроса данных с помощью библиотеки axios
или встроенного fetch
и записи полученных данных в локальное состояние компонента с помощью функции обновления состояния (сеттера), возвращаемой хуком useState
.
Пример получения списка статей:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function App() {
const [data, setData] = useState({ hits: [] })
useEffect(async () => {
const result = await axios(
'http://hn.algolia.com/api/v1/search?query=react',
)
setData({ hits: result.data })
}, [])
return (
<ul>
{data.hits.map((item) => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)
}
export default App
Обратите внимание: мы указываем пустой массив в качестве второго аргумента хука во избежание его активации при обновлении компонента. Поэтому хук запускает эффект только один раз во время монтирования компонента.
Охватывают ли хуки все случаи использования классовых компонентов?
Хуки пока охватывают не все случаи использования классовых компонентов, однако такие планы имеются. В настоящее время не существует хуков, эквивалентых методам жизненного цикла getSnapshotBeforeUpdate
и componentDidCatch
.
Почему мы используем деструктуризацию массива в useState()
?
При определении переменной состояния с помощью хука useState
, возвращается массив с двумя элементами. Первым элементом является текущее значение состояния, а вторым - функция для обновления этого значения. Использование [0]
и [1]
для доступа к указанным элементам массива нецелесообразно, поскольку они имеют вполне определенный смысл. Поэтому мы используем деструктуризацию массива.
Пример получения элемента массива по индексу:
const userStateVariable = useState('userProfile') // возвращается пара элементов
const user = userStateVariable[0] // получаем первый элемент
const setUser = userStateVariable[1] // получаем второй элемент
Пример получения элементов массива с помощью деструктуризации:
const [user, setUser] = useState('userProfile')
Что послужило источниками вдохновения при разработке хуков?
Некоторые из таких источников:
- Эксперименты с функциональным
API
в репозиторииreact-future
. - Эксперименты сообщества с
API
рендер-пропов, такие как компонентReactions
. - Переменные и ячейки состояния в
DisplayScript
. - Подписки в
RxJS
. - Компоненты-редукторы в
ReasonReact
.
Как получить доступ к императивному интерфейсу веб-компонентов?
Веб-компоненты часто предоставляют императивный API
для реализации их функциональности. Для прямого взаимодействия с узлами DOM
можно использовать рефы (ссылки, refs). Однако, при использовании сторонних библиотек веб-компонентов, лучшим решением является создание React-компонента
, оборачивающего веб-компонент.
Что такое Formik
?
Formik
- это небольшая библиотека для React
, решающая 3 основные задачи:
- Запись значений и их получение из состояния формы.
- Валидация и генерация сообщений об ошибках.
- Обработка отправки формы.
Назовите популярные решения для работы с асинхронным кодом в Redux
?
Среди популярных решений (посредников, middlewares) для работы с асинхронным кодом в экосистеме Redux
можно назвать Redux Thunk
, Redux Promise
, Redux Saga
и др.
Понимают ли браузеры JSX
?
Нет, браузеры его не понимают. Для преобразования JSX
в обычный JavaScript
нужен транспилятор. Чаще всего в этой роли выступает Babel
.
Опишите поток данных в React
React
реализует однонаправленный реактивный поток данных (one-directional reactive data flow) с помощью пропов, что ускоряет рендеринг и легче в изучении, чем традиционное двустороннее связывание данных.
Что такое React Scripts
?
react-scripts
- это набор скриптов из интерфейса командной строки (CLI
) create-react-app
, который позволяет быстро начать разработку React-приложения
без необходимости предварительной настройки вспомогательных инструментов. Команда react-scripts start
, например, запускает сервер для разработки с возможностью "горячей" перезагрузки модулей.
Назовите основные возможности Create React App
Список некоторых основных возможностей, предоставляемых CRA
:
- Поддержка синтаксиса
ES6
,React
,JSX
,Flow
иTypeScript
. - Автоматическое добавление вендорных префиксов к стилям
CSS
. - Сброс/нормализация стилей.
- "Живой" (live) сервер для разработки.
- Быстрый интерактивный запуск юнит-тестов со встроенной поддержкой отчетов о покрытии кода.
- Встроенный скрипт для сборки
JS
,CSS
, изображений и других статических файлов для продакшна с хешами и картами ресурсов (source maps). - Сервис-воркер (service worker) и файл манифеста (
manifest
), полностью отвечающие критериям прогрессивных веб-приложений (Progressive Web Apps, PWA).
Для чего используется ReactDOMServer.renderToNodeStream()
?
ReactDOMServer.renderToNodeStream()
используется для генерации HTML
на стороне сервера и отправки готовой разметки в ответ на запрос от клиента для ускорения загрузки страницы. Это способствует улучшению SEO
, поскольку поисковики индексируют статические страницы лучше, чем SPA
(Single Page Applications - одностраничные приложения).
Обратите внимание: данный метод доступен только на сервере.
Что такое MobX
?
MobX
- это простое, легко масштабируемое и "проверенное в боях" решение для управления состоянием приложений, позволяющее использовать реактивное функциональное программирование (TFRP
). Для React-приложений
требуется установка двух библиотек:
yarn add mobx mobx-react
# или
npm i mobx mobx-react
В чем разница между Redux
и MobX
?
Основные различия между Redux
и MobX
состоят в следующем:
Критерий | Redux | MobX |
---|---|---|
Определение | Это JavaScript-библиотека для управления состоянием приложения | Это JavaScript-библиотека для реактивного управления состоянием приложения |
Программирование | В основном написана с помощью синтаксиса ES6 | Написана на ES5 |
Хранилище данных | Единственное большое хранилище для всех данных | Возможно использование нескольких хранилищ |
Использование | В основном используется для больших и сложных приложений | Используется для небольших и средних приложений |
Производительность | Нуждается в улучшении | Обеспечивает наилучшую производительность |
Хранение данных | Используются обычные объекты | Используются наблюдаемые (observable ) объекты |
Обязательно ли изучать ES6
перед изучением React
?
Нет, это не обязательно. Однако многие ресурсы, посвященные React
, активно используют синтаксис ES6
. Вот парочка примеров:
- Деструктуризация: для получения пропов и их использования в компоненте.
// ES5
var someData = this.props.someData
var dispatch = this.props.dispatch
// ES6
const { someData, dispatch } = this.props
- Spread-оператор: помогает передавать пропы компоненту.
// ES5
<SomeComponent someData={this.props.someData} dispatch={this.props.dispatch} />
// ES6
<SomeComponent {...this.props} />
- Стрелочные функции: делают синтаксис более компактным.
// ES5
var users = usersList.map(function (user) {
return <li>{user.name}</li>
})
// ES6
const users = usersList.map(user => <li>{user.name}</li>)
Что такое конкурентный рендеринг?
Конкурентный рендеринг (Concurrent Rendering) делает React-приложения
более отзывчивыми за счет рендеринга дерева компонентов без блокировки основного пользовательского интерфейса. Он позволяет React
откладывать длительный рендеринг для обработки приоритетных событий. При включении данного режима React
начинает следить за появлением задач с более высоким приоритетом и при появлении таких задач приостанавливает текущий рендеринг, позволяя им выполниться в первоочередном порядке. Конкурентный режим можно включить 2 способами:
// часть приложения
<React.unstable_ConcurrentMode>
<Something />
</React.unstable_ConcurrentMode>
// приложение целиком
ReactDOM.unstable_createRoot(domNode).render(<App />)
В чем разница между асинхронным и конкурентным режимами?
По сути, они делают одно и то же. Раньше "конкурентный" режим назывался "асинхронным". Название было изменено, чтобы подчеркнуть возможность React
выполнять работу на разных "приоритетных" уровнях. Также это позволяет не путать его с другими подходами к реализации асинхронного рендеринга.
Можно ли использовать встроенный JavaScript
в React
?
Да, мы можем использовать встроенный JavaScript
, но будем получать предупреждение в консоли. Дело в том, что URL
, начинающиеся с javascript:
, представляют опасность включения необезвреженных/небезопасных строк в такие теги, как a
, тем самым создавая дыру в безопасности:
const companyProfile = {
website: "javascript: alert('Ваш сайт взломан!')",
}
// в консоль будет выведено предупреждение
<a href={companyProfile.website}>Подробнее</a>
В будущем при использовании встроенного JavaScript
будет выбрасываться исключение.
Для чего предназначен плагин ESlint
для хуков?
Данный плагин следит за соблюдением правил использования хуков. Он исходит из предположения, что любая функция, начинающаяся с use
и следующей за ней большой буквы, является хуком. Эти правила следующие:
- Хуки можно вызывать только внутри компонентов или других хуков (в том числе, пользовательских).
- При каждом рендере хуки вызываются в том порядке, в котором они были определены.
В чем разница между императивным и декларативным кодом в React
?
Представьте простой UI-компонент
, такой как кнопка "Нравится". При нажатии этой кнопки цвет ее фона меняется с серого на голубой и наоборот.
Императивный способ реализации такой кнопки выглядит следующим образом:
if ( user.likes() ) {
if ( hasBlue() ) {
removeBlue()
addGrey()
} else {
removeGrey()
addBlue()
}
}
Требуется проверить, что отображается на экране в данный момент, выполнить все необходимые операции по перерисовке элемента на основе текущего состояния приложения, включая отмену изменений, вызванных предыдущим состоянием. Представьте, каким сложным это может быть в реальном приложении.
Декларативный подход выглядит так:
if ( this.state.liked ) {
return <BlueLike />
} else {
return <GreyLike />
}
Таким образом, декларативный подход разделяет ответственность. Требуется всего лишь определить, как должен выглядеть интерфейс на основе текущего состояния приложения. Кроме того, такой синтаксис легче читать и поддерживать.
В чем заключаются преимущества использования TypeScript
?
Некоторые из основных преимуществ, предоставляемых использованием TypeScript
в React-приложениях
:
- Возможность использования самых последних "фич"
JavaScript
. - Возможность использования интерфейсов и псевдонимов для определения сложных типов.
IDE
, такие какVSCode
, "заточены" подTypeScript
.- Возможность устранять ошибки на ранней стадии разработки.
Как обеспечить сохранение информации об аутентификации пользователя между сессиями с помощью контекста?
Когда пользователь входит в систему и перезагружает страницу, для сохранения состояния мы обычно используем операцию по загрузке данных пользователя в хуке useEffect
в главном файле (App.js
). Это легко реализовать с помощью Redux
:
App.js
:
import { loadUser } from './actions/auth'
store.dispatch(loadUser())
Для того, чтобы получить доступ к контексту в App.js
, необходимо обернуть компонент App
в компонент AuthState
в index.js
. После этого при перезагрузке страницы независимо от того, где мы находимся, пользователь останется аутентифицированным/авторизованным, поскольку операция loadUser()
будет запускаться при каждом рендеринге.
index.js
:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import AuthState from './context/auth/AuthState'
ReactDOM.render(
<React.StrictMode>
<AuthState>
<App />
</AuthState>
</React.StrictMode>,
document.getElementById('root')
)
App.js
:
const authContext = useContext(AuthContext)
const { loadUser } = authContext
useEffect(() => {
loadUser()
},[])
loadUser.js
:
const loadUser = async () => {
const token = sessionStorage.getItem('token')
if(!token){
dispatch({
type: TOKEN_ERROR
})
}
setAuthToken(token)
try {
const res = await axios('/api/auth')
dispatch({
type: USER_LOADED,
payload: res.data.data
})
} catch (err) {
console.error(err)
}
}
В чем заключаются преимущества нового способа преобразования JSX
?
Тремя главными преимуществами этого являются:
- Возможность использования
JSX
без импортаReact
. - Немного уменьшается размер "бандла".
- Будущие реализации приведут к уменьшению количества концепций, знание которых требуется для овладения
React
.
Чем новый способ преобразования JSX
отличается от старого?
Новый способ не требует присутствия React
в области видимости, т.е. в простых случаях нам не нужно импортировать React
в файле компонента.
Старый способ:
import React from 'react'
function App() {
return <h1>Доброе утро!</h1>
}
Данный JSX
будет преобразован в JavaScript
следующим образом:
import React from 'react'
function App() {
return React.createElement('h1', null, 'Доброе утро!')
}
Новый способ:
function App() {
return <h1>Доброе утро!</h1>
}
Под капотом JSX
трансформируется в такой код:
import { jsx as _jsx } from 'react/jsx-runtime'
function App() {
return _jsx('h1', { children: 'Доброе утро!' })
}