Вопросы и ответы React собеседования 2023 - Часть 1

2 года назад·6 мин. на чтение

Актуальный список вопросов и ответов по ReactJS на собеседовании 2023

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

React создает виртуальный DOM. Когда состояние компонента изменяется, он сначала запускает алгоритм сравнения, который идентифицирует, что изменилось в виртуальном DOM. Второй шаг — согласование (reconciliation), при котором DOM обновляется результатами из сравнения.

2 Каковы достоинства использования React?

  • Легко узнать, как визуализируется компонент, вам просто нужно взглянуть на функцию рендеринга.
  • JSX упрощает чтение кода ваших компонентов. Также очень легко увидеть макет или то, как компоненты подключены/объединены.
  • Вы можете рендерить React на стороне сервера. Это улучшает SEO и производительность.
  • Легко тестировать.

3. В чем разница между презентационным компонентом и компонентом-контейнером?

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

4. Каковы различия между компонентом класса и функциональным компонентом?

Компонент класса использует синтаксис класса ES6 и расширяет компоненты React с помощью метода рендеринга, который возвращает элементы React. Функциональные компоненты с хуками — это обычные JavaScript функции, которые также возвращают элементы React. До появления хуков функциональные компоненты не могли иметь состояния.

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

Состояние — это структура данных, которая начинается со значения по умолчанию при монтировании компонента. Он может видоизменяться со временем, в основном в результате пользовательских событий. Пропсы (сокращение от properties, props) — это конфигурация компонента. Они получены сверху из компонента родителя и неизменяемы (иммутабельны). Компонент не может изменять свои пропсы. Функции обратного вызова также можно передавать в качестве пропсов.

6. Какие существуют методы жизненного цикла?

  • componentWillMount (устаревший) — чаще всего используется для настройки приложения в корневом компоненте.
  • componentDidMount — здесь вы можете сделать всю настройку, которую вы не смогли бы сделать без DOM, и начать получать все необходимые вам данные. Кроме того, если вы хотите настроить прослушиватели событий и т. д., этот хук жизненного цикла — хорошее место для этого.
  • componentWillReceiveProps (устаревший) — этот жизненный цикл действует на определенные изменения пропсов, чтобы вызвать переходы состояний.
  • shouldComponentUpdate — если вы беспокоитесь о потраченных впустую рендерах, shouldComponentUpdate — отличное место для повышения производительности, поскольку оно позволяет предотвратить повторный рендеринг, если компонент получает новый проп. shouldComponentUpdate всегда должен возвращать логическое значение, и на его основе будет определяться, будет ли компонент перерисован или нет.
  • componentWillUpdate (устаревший) — используется редко. Его можно использовать вместо componentWillReceiveProps для компонента, у которого также есть shouldComponentUpdate (но нет доступа к предыдущим значениям пропсов).
  • componentDidUpdate — также обычно используется для обновления DOM в ответ на изменение пропсов или состояния.
  • componentWillUnmount — позволяет отменить любые исходящие сетевые запросы или удалить все прослушиватели событий, связанные с компонентом. Вызывается перед размонтированием компонента.

7. Что такое хуки в React?

Хуки позволяют вам использовать больше функций React без использования классов. Первый хук, с которым вы, скорее всего, столкнетесь, это useState. useState — это хук, который позволяет добавлять состояние React к компонентам-функциям. Он возвращает массив с геттером и сеттером. Синтаксис выглядит так:
const [count, setCount] = React.useState(0);

<button onClick={() => setCount(count + 1)}>Increase Count</button>;
Эквивалентом при использовании компонента класса будет.
this.state = {
  count: 0,
};

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
  Increase Count
</button>;
Следующий хук, с которым вы, скорее всего, столкнетесь, это useEffect. Хук эффектов позволяет выполнять побочные эффекты в функциональных компонентах. Передача пустого массива в качестве второго аргумента useEffect эквивалентна использованию componentDidMount. Если вы передадите значение в массив, функция useEffect будет вызываться только при обновлении значения в массиве.
useEffect(() => {
  // делаем что-то, когда компонент монтируется
}, []);
Подробнее о хуках:

8. Где стоит выполнять AJAX/API запрос в классовом React компоненте?

componentDidMount — это место, где должен быть сделан запрос AJAX в компоненте React. Этот метод будет выполнен, когда компонент монтируется (добавляется в DOM) в первый раз. Этот метод выполняется только один раз в течение жизненного цикла компонента.

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

В HTML элементы формы, такие как <input>, <textarea> и <select>, обычно поддерживают свое собственное состояние и обновляют его на основе ввода пользователя. Когда пользователь отправляет форму, значения из упомянутых элементов отправляются вместе с формой. С React это работает иначе. Компонент, содержащий форму, будет отслеживать значение ввода в своем состоянии и будет повторно отображать компонент каждый раз, когда запускается функция обратного вызова, например, onChange, когда состояние будет обновлено. Элемент формы ввода, значение которого управляется React таким образом, называется управляемым компонентом. Видео про управляемые компоненты.

10. Для чего в React используются рефы?

Рефы используются для получения ссылки на DOM узел или экземпляр компонента в React. Хорошими примерами использования ссылок являются управление фокусом/выбором текста, запуск императивной анимации или интеграция со сторонними DOM библиотеками.

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

Компонент высшего порядка (Higher Order Component, HOC) — это компонент, который принимает компонент и возвращает новый компонент. HOC позволяют повторно использовать код и инициализировать компонент. Наиболее популярным HOC является функция connect из Redux. Помимо простого совместного использования библиотек и простой композиции, HOC — лучший способ хранить общее поведение между React компонентами. Если вы обнаружите, что пишете много кода в разных местах, который делает одно и то же, вы можете реорганизовать этот код в многоразовый HOC. Видео по теме:

12. Какие преимущества дает использование стрелочных функций?

  • Безопасность области действия: до появления стрелочных функций каждая новая функция определяла собственное значение this (новый объект в случае конструктора, undefined в вызовах функций в строгом режиме или базовый объект, если функция вызывается как «метод объекта» и т. д.). Стрелочная функция не создает собственного this, используется значение this окружающего контекста выполнения.
  • Компактность: стрелочные функции легче читать и писать.
  • В классовых React компонентах стрелочные функции удобнее использовать для того чтобы избежать байндинга методов при передаче их в качестве функций обратного вызова.

13. Как бы вы предотвратили рендеринг классового компонента?

Возврат null из метода рендеринга компонента означает, что ничего не будет отображаться, но это не влияет на срабатывание методов жизненного цикла компонента. Если количество повторных рендеров компонента является проблемой, доступны два варианта. Ручная реализация проверки в методе жизненного цикла shouldComponentUpdate.
shouldComponentUpdate(nextProps, nextState){
  const allowRender = true;
  // Делаем здесь некоторую проверку и назначаем решение в allowRender
  return allowRender
}
Или использовать React.PureComponent вместо React.Component. React.PureComponent реализует shouldComponentUpdate() с поверхностным сравнением пропсов и состояний. Это позволяет избежать повторного рендеринга компонента со значениями просов и состояний, которые не поменялись.

14. Что такое key при рендеринге списка и для чего он нужен?

Key помогают React определить, какие элементы были изменены, добавлены или удалены. Ключи должны быть даны элементам внутри массива, чтобы однозначно идентифицировать элементы. Лучший способ выбрать ключ — использовать строку, которая однозначно идентифицирует элемент списка среди его соседей, например, id. Чаще всего вы будете использовать идентификаторы из ваших данных в качестве ключей. Если у вас нет стабильных идентификаторов для отображаемых элементов, вы в крайнем случае можете использовать индекс элемента в качестве ключа. Не рекомендуется использовать индексы для ключей, т.к. это может привести к неверному отображению элементов в случае их удаления или изменения порядка. Также key можно использовать для пересоздания компонентов.

15. Для чего нужно вызывать super(props)?

Конструктор дочернего класса не может использовать this до тех пор, пока не будет вызвана функция super(). Кроме того, конструкторы классов ES2015 должны вызывать super(), если они являются подклассами. Причина передачи пропса в super() заключается в том, чтобы вы могли получить доступ к this.props в конструкторе. Хочешь узнать больше? Еще вопросы с собеседований.

Состояние как снимок в React

2 года назад·5 мин. на чтение

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

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

Установка состояния запускает рендеринг

Вы можете думать о своем пользовательском интерфейсе как об изменении непосредственно в ответ на пользовательское событие, такое как клик. В React это работает немного иначе, чем эта ментальная модель. На предыдущей странице вы видели, что изменение состояния запрашивает повторный рендеринг. Это означает, что для того, чтобы интерфейс отреагировал на событие, вам необходимо обновить состояние. В этом примере, когда вы нажимаете "send", setIsSent(true) сообщает React повторно отобразить пользовательский интерфейс:
import { useState } from 'react';

export default function Form() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('Hi!');
  if (isSent) {
    return <h1>Your message is on its way!</h1>;
  }
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        setIsSent(true);
        sendMessage(message);
      }}
    >
      <textarea
        placeholder="Message"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

function sendMessage(message) {
  // ...
}
Вот что происходит, когда вы нажимаете кнопку:
  1. Выполняется обработчик события onSubmit.
  2. setIsSent(true) устанавливает isSent в true и кладет в очередь новый рендеринг.
  3. React повторно отображает компонент в соответствии с новым значением isSent.
Давайте подробнее рассмотрим взаимосвязь между состоянием и рендерингом.

Рендеринг делает снимок во времени

"Рендеринг" означает, что React вызывает ваш компонент, который является функцией. JSX, который вы возвращаете из этой функции, подобен снимку пользовательского интерфейса в момент времени. Его пропсы, обработчики событий и локальные переменные были рассчитаны с использованием его состояния во время рендеринга. В отличие от фотографии или кадра фильма, "снимок" пользовательского интерфейса, который вы возвращаете, является интерактивным. Он включает в себя логику, такую ​​как обработчики событий, которые определяют, что происходит в ответ на входные данные. Затем React обновляет экран в соответствии с этим снимком и подключает обработчики событий. В результате нажатие кнопки вызовет обработчик кликов из вашего JSX. Когда React перерисовывает компонент:
  1. React снова вызывает вашу функцию.
  2. Ваша функция возвращает новый снимок JSX.
  3. Затем React обновляет экран в соответствии со снимком, который вы вернули.
Состояние, как память компонента, не похоже на обычную переменную, которая исчезает после возврата из вашей функции. Состояние на самом деле «живет» в самом React — как будто на полке! — вне вашей функции. Когда React вызывает ваш компонент, он дает вам снимок состояния для этого конкретного рендеринга. Ваш компонент возвращает снимок пользовательского интерфейса со свежим набором пропсов и обработчиков событий в своем JSX, и все они рассчитаны с использованием значений состояния из этого рендеринга.
Вот небольшой эксперимент, чтобы показать вам, как это работает. В этом примере вы можете ожидать, что нажатие кнопки «+3» увеличит счетчик на три, потому что он вызывается setNumber(number + 1) три раза. Посмотрите, что происходит, когда вы нажимаете кнопку «+3»:
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
        }}
      >
        +3
      </button>
    </>
  );
}
Обратите внимание, что number увеличивается только один раз за клик! Установка состояния изменяет его только для следующего рендера. Во время первого рендера number было 0. Вот почему в обработчике этого рендеринга onClick значение number по-прежнему остается 0 даже после вызова setNumber(number + 1):
<button
  onClick={() => {
    setNumber(number + 1);
    setNumber(number + 1);
    setNumber(number + 1);
  }}
>
  +3
</button>
Вот что обработчик нажатия этой кнопки сообщает React:
  1. setNumber(number + 1): number равно 0 таким образом setNumber(0 + 1).
    • React готовится изменить number на 1 в следующем рендеринге.
  2. setNumber(number + 1): number равно 0 таким образом setNumber(0 + 1).
    • React готовится изменить number на 1 в следующем рендеринге.
  3. setNumber(number + 1): number равно 0 таким образом setNumber(0 + 1).
    • React готовится изменить number на 1 в следующем рендеринге.
Несмотря на то, что вы вызвали setNumber(number + 1) три раза, в этом рендере обработчик событий number всегда равен 0, поэтому вы устанавливаете состояние 1 три раза. Вот почему после завершения обработчика событий React повторно отображает компонент с number равным 1, а не 3. Вы также можете визуализировать это, мысленно заменяя переменные состояния их значениями в своем коде. Поскольку number переменная состояния равна 0 для этого рендера, его обработчик событий выглядит так:
<button
  onClick={() => {
    setNumber(0 + 1);
    setNumber(0 + 1);
    setNumber(0 + 1);
  }}
>
  +3
</button>
Для следующего рендера number равен 1, так что обработчик клика этого рендера выглядит так:
<button
  onClick={() => {
    setNumber(1 + 1);
    setNumber(1 + 1);
    setNumber(1 + 1);
  }}
>
  +3
</button>
Вот почему повторное нажатие на кнопку установит счетчик на 2, затем 3 на следующий клик и т.д.

Состояние с течением времени

Попробуйте угадать, какое сообщение будет выведено в alert при нажатии этой кнопки:
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 5);
          alert(number);
        }}
      >
        +5
      </button>
    </>
  );
}
Если вы используете метод замены из предыдущего, вы можете догадаться, что предупреждение покажет 0:
setNumber(0 + 5);
alert(0);
Но что, если положите alert в таймер, чтобы оно срабатывало только после повторного рендеринга компонента? Будет ли он показывать 0 или 5?
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 5);
          setTimeout(() => {
            alert(number);
          }, 3000);
        }}
      >
        +5
      </button>
    </>
  );
}
Если вы используете метод подстановки, вы можете увидеть «снимок» состояния, переданного в alert.
setNumber(0 + 5);
setTimeout(() => {
  alert(0);
}, 3000);
Состояние, хранящееся в React, могло измениться к моменту запуска оповещения, но оно было запланировано с использованием снимка состояния на момент взаимодействия с ним пользователя. Значение переменной состояния никогда не изменяется в процессе рендеринга, даже если код обработчика событий является асинхронным. Внутри onClick этого рендера значение number продолжает оставаться 0 даже после вызова setNumber(number + 5). Его значение было «фиксировано», когда React «сделал снимок» пользовательского интерфейса, вызвав ваш компонент.
Вот пример того, как это делает ваши обработчики событий менее подверженными ошибкам синхронизации. Ниже представлена ​​форма, которая отправляет сообщение с пятисекундной задержкой. Представьте себе этот сценарий:
  1. Вы нажимаете кнопку «Отправить», отправляя «Привет» Алисе.
  2. Прежде чем закончится пятисекундная задержка, вы измените значение поля «Кому» на «Боб».
Что будет выведено в alert? Будет ли он отображать «Вы поздоровались с Алисой»? Или он будет отображать «Вы поздоровались с Бобом»? Сделайте предположение, основанное на том, что вы знаете:
import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`You said ${message} to ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        To:{' '}
        <select value={to} onChange={(e) => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}
React сохраняет значения состояния «фиксированными» в обработчиках событий одного рендеринга. Вам не нужно беспокоиться о том, изменилось ли состояние во время выполнения кода. Но что, если вы хотите прочитать последнее состояние перед повторным рендерингом? Вы захотите использовать функцию обновления состояния, описанную на следующей странице.

Резюме

  • Состояние установки запрашивает новый рендеринг.
  • React хранит состояние вне вашего компонента, как на полке.
  • Когда вы вызываете useState, React дает вам снимок состояния для этого рендеринга .
  • Переменные и обработчики событий не «выживают» при повторном рендеринге. Каждый рендер имеет свои обработчики событий.
  • Каждый рендер (и функции внутри него) всегда будет «видеть» снимок состояния, которое React дал этому рендеру.
  • Вы можете мысленно заменить состояние в обработчиках событий, подобно тому, как вы думаете об отрендеренном JSX.
  • Обработчики событий, созданные в прошлом, имеют значения состояния из рендеринга, в котором они были созданы.