⚛️ 45 вопросов для подготовки к собеседованию по React

Разбираем вопросы, ответы на которые должен знать каждый React-разработчик.

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

1. Какие хуки вы знаете?

Какие хуки в React вы знаете?

Хуки – это функции-крючки, с помощью которых можно «прицепиться» к состоянию и методам жизненного цикла React из функциональных компонентов. Хуки мощные инструменты для управления состоянием, обработки побочных эффектов и повторного использования логики:

Хук Используется для...
React.useState управления состоянием в функциональных компонентах
useEffect выполнения побочных эффектов в функциональных компонентах (например, для получения данных или подписки на события)
useContext доступа к значению контекста React в функциональном компоненте
useRef создания изменяемых ссылок на элементы или значения, которые сохраняются во время рендеринга
useCallback мемоизации функций для предотвращения ненужных повторных рендеров
useMemo запоминания значений с целью повышения производительности за счет кэширования ресурсоемких вычислений
useReducer управления состоянием с помощью функции reducer, аналогично тому, как это делается в Redux
useLayoutEffect выполнения побочных эффектов подобно useEffect, с той разницей, что эффект запускается синхронно после всех мутаций DOM

Подробнее о хуках – в документации.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека фронтендера»

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

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

Виртуальный DOM в React – концепция создания и хранения в памяти облегченного виртуального представления реальной DOM (объектной модели документа). Этот подход помогает оптимизировать производительность веб-приложений:

  • Если данные или состояние React-компонента изменяются, вместо прямых манипуляций с реальной DOM-моделью обновляется виртуальная.
  • Затем в виртуальной модели DOM вычисляется разница между предыдущим и обновленным состоянием компонента (с помощью diffing-алгоритма).
  • После выявления различий React эффективно обновляет только необходимые части реальной DOM для отражения изменений.

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

3. Как выполнить рендеринг элементов массива?

Для рендеринга статического списка можно использовать метод map(), который перебирает элементы и возвращает новый массив элементов React:

const languages = [
  "JavaScript",
  "TypeScript",
  "Python",
];

function App() {
  return (
    <div>
      <ul>{languages.map((language) => <li>{language}</li>)}</ul>
    </div>
  );
}

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

import React, { useState } from 'react';

function App() {
 const [clothingItems, setClothingItems] = useState([
   { id: 1, name: "Футболка", size: "M" },
   { id: 2, name: "Шорты", size: "L" },
   { id: 3, name: "Брюки", size: "XL" },
   { id: 4, name: "Платье", size: "XS" },
   { id: 5, name: "Свитер", size: "L" },
 ]);


 return (
   <div className="App">
     <h1>Новые товары</h1>
     <ul>
       {clothingItems.map((item) => (
         <li key={item.id}>
           <h2>{item.name}</h2>
           <p>Размер: {item.size}</p>
         </li>
       ))}
     </ul>
   </div>
 );
}

export default App;

Подробнее о рендеринге списков – в документации.

4. Чем управляемые компоненты отличаются от неуправляемых?

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

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

import { useState } from 'react'; 

function App() { 
  const [value, setValue] = useState(''); 

  return ( 
    <div> 
      <h3>Controlled Component</h3> 
      <input name="name" value={name} onChange={(e) => setValue(e.target.value)} />
      <button onClick={() => console.log(value)}>Get Value</button> 
    </div> 
  ); 
}

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

import { useRef } from 'react'; 

function App() { 
  const inputRef = useRef(null); 

  return ( 
    <div className="App"> 
      <h3>Uncontrolled Component</h3> 
      <input type="text" name="name" ref={inputRef} /> 
      <button onClick={() => console.log(inputRef.current.value)}>Get Value</button> 
    </div> 
  ); 
} 

Подробнее о контролируемых и неконтролируемых компонентах – в документации.

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

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

Классовые компоненты определяются как классы ES6 и расширяют класс React.Component. Они используют метод render для возврата JSX (JavaScript XML), определяющего вывод компонента. Классовые компоненты имеют доступ к методам жизненного цикла компонента и управлению состоянием через this.state и this.setState().

class App extends React.Component {
  state = {
    value: 0,
  };

  handleAgeChange = () => {
    this.setState({
      value: this.state.value + 1 
    });
  };

  render() {
    return (
      <>
        <p>Value is {this.state.value}</p>
        <button onClick={this.handleAgeChange}>
        Increment value
        </button>
      </>
    );
  }
}

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

import { useState } from 'react';

const App = () => {
  const [value, setValue] = useState(0);

  const handleAgeChange = () => {
    setValue(value + 1);
  };

  return (
      <>
        <p>Value is {value}</p>
        <button onClick={handleAgeChange}>
        Increment value
        </button>
      </>
  );
}

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

6. Что такое методы жизненного цикла компонента?

Что такое методы жизненного цикла компонента в React?

Методы жизненного цикла – это способ подключения к различным этапам жизненного цикла компонента, позволяющий выполнять определенный код в определенное время. Вот список основных методов:

  • constructor – первый метод, вызываемый при создании компонента. Он используется для инициализации состояния и привязки обработчиков событий. В функциональных компонентах для аналогичных целей используется хук useState.
  • render – отвечает за рендеринг JSX-разметки и возвращает содержимое, которое будет выведено на экран.
  • componentDidMount – вызывается сразу после рендеринга компонента в DOM. Обычно используется для задач инициализации, таких как вызов API или настройка слушателей событий.
  • componentDidUpdate – вызывается при изменении реквизитов или состояния компонента. Позволяет выполнять побочные эффекты, обновлять компонент на основе изменений или запускать дополнительные вызовы API.
  • componentWillUnmount – вызывается непосредственно перед удалением компонента из DOM. Используется для очистки ресурсов, которые были установлены в componentDidMount, например, для удаления слушателей событий или отмены таймеров.

Некоторые методы жизненного цикла (componentWillMount, componentWillReceiveProps и componentWillUpdate) уже устарели, вместо них следует использовать альтернативные методы или хуки.

7. В чем заключаются особенности использования useState?

useState возвращает значение состояния и функцию для его обновления.

const [value, setValue] = useState('Some state');

Во время первого рендеринга возвращаемое состояние соответствует значению, переданному в качестве первого аргумента. Для обновления состояния используется функция setState. Она принимает в качестве параметра новое значение состояния и ставит компонент в очередь на повторный рендеринг. Функция setState может также принимать в качестве параметра функцию обратного вызова, которая принимает в качестве параметра предыдущее значение состояния.

Подробнее о useState – в документации.

8. В чем заключаются особенности использования useEffect?

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

Вместо этого рекомендуется использовать useEffect. Функция, переданная в useEffect, будет выполняться после фазы фиксации, а если в качестве второго параметра передать массив зависимостей, то функция будет вызываться каждый раз, когда одна из зависимостей изменяется.

useEffect(() => {
  console.log('Logging something');
}, [])

Подробнее о useEffect – в документации.

9. Как отследить размонтирование функционального компонента?

Очень часто useEffect создает ресурсы, которые необходимо очистить или сбросить до того, как компонент покинет экран, например подписку или идентификатор таймера.

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

useEffect(() => {
  function handleChange(value) {
    setValue(value);
  }
  SomeAPI.doFunction(id, handleChange);

  return function cleanup() {
    SomeAPI.undoFunction(id, handleChange);
  };
})

10. Что такое реквизиты в React?

Реквизиты (props, пропсы) – это данные, которые передаются компоненту от родителя. Реквизиты доступны только для чтения и не могут быть изменены.

// Родительский компонент
const Parent = () => {
  const data = "Hello, World!";

  return (
    <div>
      <Child data={data} />
    </div>
  );
};

// дочерний компонент
const Child = ({ data }) => {
  return <div>{data}</div>;
};

Подробнее о реквизитах – в документации.

11. Что такое менеджер состояний и с какими из них вы работали?

Что такое менеджер состояний и с какими из них вы работали?

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

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

Роль менеджера состояний может исполнять React Context, но лучше использовать библиотеки Redux или MobX.

12. В каких случаях можно использовать локальное состояние, а когда следует использовать глобальное состояние?

Локальное состояние следует применять в тех случаях, когда оно:

  • Используется только в рамках одного компонента и не передается другим компонентам.
  • Используется в компоненте, который представляет собой отдельный элемент списка.

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

13. Что такое reducer в Redux и какие параметры он принимает?

Редуктор – это чистая функция, принимающая в качестве параметров состояние и действие. Внутри редуктора мы отслеживаем тип полученного действия и, в зависимости от него, модифицируем состояние и возвращаем новый объект состояния.

export default function appReducer(state = initialState, action) {
  // The reducer normally looks at the action type field to decide what happens
  switch (action.type) {
    // Do something here based on the different types of actions
    default:
      // If this reducer doesn't recognize the action type, or doesn't
      // care about this specific action, return the existing state unchanged
      return state
  }
}

Подробнее о редукторах – здесь.

14. Что такое действие и как можно изменить состояние в Redux?

Действие – это простой объект JavaScript, который должен иметь поле с типом:

{
  type: "SOME_TYPE"
}

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

{
  type: "SOME_TYPE",
  payload: "Any payload",
}

Подробнее о действиях.

15. Какой паттерн реализует Redux?

Какой паттерн реализует Redux?

Redux реализует паттерн Flux, который представляет собой предсказуемый шаблон управления состоянием приложений. Он помогает управлять состоянием приложения путем введения однонаправленного потока данных и централизованного хранилища для состояния приложения.

Подробнее о Flux.

16. Какой паттерн реализует Mobx?

Mobx представляет собой реализацию паттерна Наблюдатель (Observer), также известный как шаблон Издатель – Подписчик (Publish-Subscribe).

Подробнее о паттернах – здесь.

17. В чем заключаются особенности работы с Mobx?

Mobx предоставляет декораторы observable и computed для определения наблюдаемого состояния и реактивных функций. Для изменения состояния используются действия, декорированные с помощью action, что обеспечивает отслеживание всех изменений. Mobx также предлагает автоматическое отслеживание зависимостей, различные типы реакций, тонкий контроль над реактивностью и простую интеграцию с React с помощью пакета mobx-react. В целом Mobx упрощает управление состоянием, автоматизируя процесс обновления на основе изменений в наблюдаемом состоянии.

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

Получить доступ к переменной в состоянии можно с помощью декоратора observable для определения переменной как наблюдаемой. Вот пример:

import { observable, computed } from 'mobx';

class MyStore {
  @observable myVariable = 'Hello Mobx';

  @computed get capitalizedVariable() {
    return this.myVariable.toUpperCase();
  }
}

const store = new MyStore();
console.log(store.capitalizedVariable); // Выведет HELLO MOBX

store.myVariable = 'Hi Mobx';
console.log(store.capitalizedVariable); // выведет HI MOBX

В данном примере переменная myVariable определяется как наблюдаемая с помощью декоратора observable. Затем к переменной можно получить доступ с помощью store.myVariable. Любые изменения, внесенные в myVariable, будут автоматически вызывать обновления в зависимых компонентах или реакциях.

Подробнее о доступе к переменным написано здесь.

19. В чем разница между Redux и Mobx?

Redux – простая библиотека управления состояниями, которая придерживается строгого однонаправленного потока данных и принципа неизменяемости. Она использует много шаблонного кода и явных обновлений, зато отлично интегрируется с React.

Mobx, напротив, предоставляет более гибкий и интуитивно понятный API с меньшим объемом шаблонного кода. Он позволяет напрямую изменять состояние и автоматически отслеживает изменения, повышая производительность. Выбор между Redux и Mobx зависит от конкретных спецификаций проекта и личных предпочтений.

20. Что такое JSX?

Это расширение синтаксиса JavaScript. По умолчанию для создания элементов в React используется следующий синтаксис:

const someElement = React.createElement(
  'h3',
  {className: 'title__value'},
  'Some Title Value'
);

Но благодаря JSX этот код можно написать так:

const someElement = (
  <h3 className='title__value'>Some Title Value</h3>
);

JSX позволяет использовать HTML-подобную разметку внутри JavaScript. Иными словами, это синтаксический сахар для упрощения разработки и чтения кода.

21. Что такое пробрасывание пропсов (props drilling)?

Под пробрасыванием («сверлением») реквизитов понимается процесс передачи реквизитов через несколько уровней вложенных компонентов, даже если некоторые промежуточные компоненты не используют эти пропсы напрямую. Глубокое пробрасывание делает структуру кода сложной и запутанной.

// Родительский компонент
const Parent = () => {
  const data = "Hello, World!";

  return (
    <div>
      <ChildA data={data} />
    </div>
  );
};

// Промежуточный дочерний компонент
const ChildA = ({ data }) => {
  return (
    <div>
      <ChildB data={data} />
    </div>
  );
};

// Целевой дочерний компонент
const ChildB = ({ data }) => {
  return <div>{data}</div>;
};

В этом примере реквизит data передается от родительского компонента к ChildA, а затем от ChildA к ChildB, хотя ChildA напрямую не использует этот реквизит. Этот подход приведет к серьезным проблемам при наличии большого количества уровней вложенности или при необходимости доступа к данным со стороны компонентов, расположенных дальше по дереву: код станет очень сложным для понимания и поддержки.

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

// Создать контекст 
const DataContext = React.createContext();

// Обернуть родительский компонент контекстом
const Parent = () => {
  const data = "Hello World!";

  return (
    <DataContext.Provider value={data}>
      <ChildA />
    </DataContext.Provider>
  );
};

// Получить данные из контекста в вложенных компонентах
const ChildB = () => {
  const data = React.useContext(DataContext);
  
  return <div>{data}</div>; 
};

22. Как работает условный рендеринг элементов?

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

return (
  <div>
    {isVisible && <span>I'm visible!</span>}
  </div>
);
return (
  <div>
    {isOnline ? <span>I'm online!</span> : <span>I'm offline</span>}
  </div>
);
if (isOnline) {
  element = <span>I'm online!</span>;
} else {
  element = <span>I'm offline</span>;
}

return (
  <div>
    {element}
  </div>
);

Подробнее об условном рендеринге рассказано в документации.

23. Для чего и как используется UseMemo?

Хук useMemo используется для кэширования и запоминания результатов вычислений. В него передается функция, которая производит вычисления, и массив зависимостей. Хук будет пересчитывать значение только тогда, когда изменится одна из зависимостей – эта оптимизация помогает избежать ресурсозатратных вычислений при каждом рендере.

const memoValue = useMemo(() => computeFunc(paramA, paramB), [paramA, paramB]);

Подробнее о useMemo.

24. Для чего используется useCallback и как он работает?

Хук useCallback возвращает мемоизированную версию коллбэк-функции. Эта версия будет изменяться только в том случае, если изменятся значения зависимостей в массиве зависимостей, переданном в useCallback.

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

Таким образом, useCallback позволяет оптимизировать производительность за счет избежания лишних отрисовок.

const callbackValue = useCallback(() => computeFunc(paramA, paramB), [paramA, paramB]);

Подробности здесь.

25. В чем разница между useMemo и useCallback?

  • useMemo используется для запоминания результата вычислений, а useCallback – для запоминания самой функции.
  • useMemo кэширует вычисленное значение и возвращает его при последующих рендерах, если зависимости не изменились.
  • useCallback кэширует саму функцию и возвращает тот же экземпляр, если зависимости не изменились.

26. Как используют React Context?

Как используют React Context?

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

React Context API состоит из трех основных частей:

  • createContext – используется для создания нового объекта контекста.
  • Context.Provider – используется для предоставления значения контексту. Он оборачивает компоненты, которым необходим доступ к значению.
  • Context.Consumer или хук useContext – этот компонент или хук используется для получения значения из контекста. Он может быть использован в любом компоненте, входящем в состав провайдера контекста.

Использование React Context позволяет избежать передачи реквизитов через несколько уровней компонентов (prop drilling) и легко управлять состоянием на более высоком уровне – это улучшает читаемость кода и производительность приложения.

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

27. Для чего используется useContext и как он работает?

В типичном React-приложении данные передаются сверху вниз (от родительского компонента к дочернему) с помощью реквизитов (пропсов). Однако такой способ может оказаться слишком громоздким для некоторых типов реквизитов, которые нужно передавать многим компонентам – из-за языка или темы пользовательского интерфейса, например. Контекст предоставляет возможность обмена данными между компонентами без необходимости явной передачи реквизита через каждый уровень дерева.

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

const App = () => {
  const theme = useContext(ThemeContext);

  return (
    <div style={{ color: theme.palette.primary.main }}>
      Some div
    </div>
  );
}

Подробности об использовании useContext в документации.

28. Для чего используется useRef и как он работает?

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

Обычно useRef используется для доступа к дочерним элементам в императивном стиле, то есть явно обращаясь к какому-то элементу DOM. Это позволяет выполнять различные действия над элементом – изменять стили, добавлять события и т.д.

const App = () => {
  const inputRef = useRef(null);

  const buttonClick = () => {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={buttonClick}>Focus on input tag</button>
    </>
  )
}

Подробнее о useRef.

29. Что такое React.memo()?

React.memo() – это компонент высшего порядка, который используется для оптимизации производительности веб-приложений. Если компонент, который вы хотите оптимизировать, всегда отображает один и тот же результат с неизменными реквизитами (props), вы можете обернуть его вызов в React.memo, чтобы запомнить его результат. Таким образом, React может использовать последний отрендеренный результат этого компонента вместо того, чтобы снова его рендерить, если входные данные не изменились. Это помогает оптимизировать производительность, так как повторный рендеринг может быть затратным по ресурсам. Важно отметить, что React.memo влияет только на изменение входных данных (реквизитов): если функциональный компонент, обернутый в React.memo, использует useState, useReducer или useContext для изменения своего состояния или контекста, он все равно будет перерисован при изменении этих состояний или контекстов.

import { memo } from 'react';

const MemoComponent = memo(MemoComponent = (props) => {
  // ...
});

Подробности в документации.

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

Возврат нескольких элементов из компонента – обычная практика в React. Однако это может привести к созданию большого количества элементов в дереве виртуального DOM, отчего пострадает производительность приложения.

Фрагменты React позволяют группировать несколько элементов внутри функции-компонента, избегая таким образом создания большого количества узлов в виртуальном DOM. Это улучшает производительность приложения и читаемость кода.

<>
  <OneChild />
  <AnotherChild />
</>
// or
<React.Fragment>
  <OneChild />
  <AnotherChild />
</React.Fragment>

Подробнее о фрагментах.

31. Что такое React Reconciliation?

React использует алгоритм согласования (reconciliation) для определения изменений между старым и новым деревом виртуального DOM. Этот алгоритм сравнивает каждый элемент старого дерева с соответствующим элементом нового дерева и определяет, какие элементы были добавлены, удалены или обновлены. Затем React применяет эти изменения к реальному DOM-дереву, обновляя только те элементы, которые действительно изменились.

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

Подробнее о согласовании в React.

32. Зачем нужны ключи в списках при использовании map()?

Ключи в списках помогают React отслеживать изменения в списке. Когда элементы в списке обновляются, удаляются или добавляются, React использует ключи для определения того, какие изменения произошли и как обновить DOM. Без ключей React не сможет эффективно обновлять список, что может привести к снижению производительности. При использовании map() необходимо указывать ключи, чтобы React мог связать каждый элемент списка с соответствующей записью в массиве. При выборе ключа лучше использовать уникальные идентификаторы элементов, чтобы React мог легко определить, какой элемент был изменен.

const languages = [
  {
    id: 1,
    lang: "JavaScript",
  },
  {
    id: 2,
    lang: "TypeScript",
  },
  {
    id: 3,
    lang: "Python",
  },
];

const App = () => {
  return (
    <div>
      <ul>{languages.map((language) => (
        <li key={`${language.id}_${language.lang}`}>{language.lang}</li>
      ))}
      </ul>
    </div>
  );
}

Подробнее о ключах в списках.

33. Как обрабатывать асинхронные действия в Redux Thunk?

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

  • Импортировать Redux Thunk как middleware.
  • Учесть, что создатели действия должны возвращать функцию вместо простого объекта. Эта функция должна принимать параметр dispatch и вызывать его с новым объектом действия.
  • Внутри этой функции можно использовать асинхронные операции, такие как fetch или API вызовы.
  • Когда асинхронная операция завершится, необходимо вызвать метод dispatch с новым объектом действия, который содержит результат операции.
export const addUser = ({ firstName, lastName }) => {
  return dispatch => {
    dispatch(addUserStart());
  }

  axios.post('https://jsonplaceholder.typicode.com/users', {
    firstName,
    lastName,
    completed: false
  })
  .then(res => {
    dispatch(addUserSuccess(res.data));
  })
  .catch(error => {
    dispatch(addUserError(error.message));
  })
}

Подробности в документации.

34. Как отследить изменения поля объекта в функциональном компоненте?

Для этого необходимо использовать хук useEffect и передать поле объекта в виде массива зависимостей:

useEffect(() => {
  console.log('Changed!')
}, [obj.someField])

35. Как получить доступ к элементу DOM?

Чтобы получить доступ к DOM-элементу в React, нужно создать ссылку на этот элемент. Это можно сделать с помощью функции React.createRef() или хука useRef(). После создания ссылки ее нужно присоединить к компоненту React, для которого нужно получить доступ к DOM, с помощью атрибута ref. Затем, чтобы получить доступ к самому элементу DOM, можно обратиться к созданной ссылке через ref.current:

const App = () => {
  const myRef = useRef(null);

  const handleClick = () => {
    console.log(myRef.current); // Accessing the DOM element
  };

  return (
    <div>
      <input type="text" ref={myRef} />
      <button onClick={handleClick}>Click Me</button>
    </div>
  );
}

export default App;

36. Что такое пользовательский хук?

Пользовательский хук в React – это функция, которая позволяет повторно использовать логику внутри компонентов. Хуки позволяют инкапсулировать определенную логику и использовать ее в разных компонентах. Названия пользовательских хуков обычно начинаются с use, и они могут вызывать другие хуки при необходимости.

Подробности в документации.

37. Что такое публичный API?

В контексте индексных файлов под Public API обычно понимается интерфейс или функции, которые открыты и доступны для внешних модулей или компонентов. Вот пример кода индексного файла, представляющего Public API:

// index.js

export function greet(name) {
  return `Hello, ${name}!`;
}

export function calculateSum(a, b) {
  return a + b;
}

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

// main.js

import { greet, calculateSum } from './index.js';

console.log(greet('John')); // Hello, John!
console.log(calculateSum(5, 3)); // 8

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

38. Перечислите правила создания пользовательского хука.

При написании такого хука нужно соблюдать несколько правил:

  • Название хука должно начинаться с use.
  • Следует использовать сначала встроенные хуки, если они решают нужную задачу, прежде чем создавать свои.
  • Нельзя вызывать хук условно. Хук надо вызывать тогда, когда это необходимо.
  • В хуке должна быть только логика, которую можно использовать повторно. Не стоит добавлять в него код, который нужен только в одном месте.
  • Хук должен быть чистой функцией.
  • Хук может возвращать значение или вызывать другие хуки. Это позволяет создавать цепочки обработки данных.
  • Название хука должно быть понятным и отражать его назначение. Это поможет другим разработчикам понять, что делает этот хук и как его можно использовать.

39. Что такое серверный рендеринг (Server-Side Rendering)?

Что такое серверный рендеринг (Server-Side Rendering)?

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

Основная цель серверного рендеринга – улучшение скорости загрузки страниц и повышение позиций в поисковом ранжировании. Используя SSR, сервер подготавливает страницу полностью, включая динамическое содержимое. В результате пользователь получает готовую к просмотру страницу, без необходимости выполнения дополнительных запросов к серверу.

Применение SSR особенно полезно для приложений, где много контента генерируется с помощью JavaScript, так как поисковые системы не могут индексировать такой контент. Используя серверный рендеринг, поисковики могут легко увидеть и проиндексировать все содержимое страницы.

Помимо React, SSR поддерживается многими современными фреймворками, например Next.js, Nuxt.js и Meteor.js.

40. Перечислите преимущества серверного рендеринга.

Серверный рендеринг имеет несколько весомых преимуществ:

  • Улучшение времени загрузки – серверный‌ рендеринг позволяет серверу отправить пользователю полностью готовую к отображению HTML-страничку, уменьшая тем самым объем обработки на стороне клиента и ускоряя загрузку страницы.
  • Улучшение видимости в поисковых системах – поисковые роботы могут легко просматривать и индексировать содержание страниц, обработанных на стороне сервера, что приводит к улучшению видимости в результатах поиска.
  • Обеспечение доступности – серверный рендеринг обеспечивает доступность контента для пользователей с отключенным JavaScript, гарантируя надежное и полное отображение всех страниц приложения.
  • Повышение производительности в условиях низкой‌ скорости интернета –серверный рендеринг уменьшает объем данных, получаемых пользователем.

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

41. Какие основные функции Next.js вы знаете?

getStaticProps – метод используется для получения данных во время сборки и предварительного рендеринга страницы в виде статического HTML. Он гарантирует, что данные будут доступны во время сборки и не изменятся при последующих запросах.

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: {
      data
    }
  };
}

getServerSideProps – используется для получения данных при каждом запросе и предварительного рендеринга страницы на сервере. Пригодится, когда необходимо получить данные, которые могут часто меняться или являются специфическими для пользователя.

export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: {
      data
    }
  };
}

getStaticPaths – используется для определения списка статичных путей, которые будут доступны во время компиляции приложения. Этот метод обычно используется для генерации данных для динамических страниц с параметрами. Например, если у вас есть динамическая страница с ID пользователя в качестве параметра, вы можете использовать getStaticPaths для определения всех возможных ID пользователей, которые могут быть использованы на этой странице. Это позволяет Next.js предварительно сгенерировать все возможные страницы, что улучшает производительность приложения.

export async function getStaticPaths() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  const paths = posts.map((post) => ({
    params: { id: post.id }
  }));

  return {
    paths,
    fallback: false
  };
}

Подробности в документации.

42. Что такое линтеры?

Что такое линтеры?

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

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

Использование линтеров дает ряд преимуществ:

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

Среди популярных линтеров можно назвать ESLint для JavaScript и Stylelint для CSS и Sass.

43. Какие архитектурные решения для React вы знаете?

Существует несколько архитектурных решений и паттернов для построения React-проектов. К числу популярных относятся:

  • MVC (Model-View-Controller). Это традиционный архитектурный паттерн, разделяющий приложение на три основных компонента – модель, представление и контроллер. React может использоваться в слое View для визуализации пользовательского интерфейса, в то время как для слоев Model и Controller могут применяться другие библиотеки или фреймворки.
  • Flux. Эта архитектура была создана разработчиками Facebook* специально для приложений React. Он основан на однонаправленной передаче данных, что упрощает понимание кода и отладку компонентов приложения.
  • Atomic Design. Это универсальная методология проектирования, которая разделяет пользовательский интерфейс на более мелкие самодостаточные компоненты многократного использования. Такие компоненты удобно комбинировать для создания более сложных пользовательских интерфейсов.
  • Паттерн «Контейнер и компонент». Этот паттерн отделяет представление (компонент) от логики и управления состоянием (контейнер). Компоненты отвечают за визуализацию пользовательского интерфейса, а контейнеры – за бизнес-логику и управление состоянием.
  • Feature-Sliced Design. Это современный архитектурный подход, используемый для организации и структурирования React-приложений. Он направлен на решение проблем масштабируемости, сопровождения и повторного использования путем разделения кодовой базы приложения на основные функциональные модули.

44. Что такое Feature-Sliced Design?

Feature-Sliced Design – это современный архитектурный подход для разработки приложений на React, который решает проблемы масштабируемости, сопровождения и повторного использования. Основная идея этого подхода заключается в разделении кодовой базы проекта на функциональные модули или «слайсы».

Каждый функциональный модуль в Feature-Sliced Design представляет собой отдельную папку, которая содержит все необходимые компоненты, логические блоки и файлы, связанные с данной функцией. Благодаря этому, мы получаем модульную и изолированную структуру кодовой базы.

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

45. Как выполняется валидация данных в React-приложении?

Как выполняется валидация данных в React-приложении?

Валидацию данных можно реализовать несколькими способами:

  • PropTypes – это встроенный в React механизм, который позволяет определить тип данных для каждого реквизита компонента. Если тип данных не соответствует ожидаемому, PropTypes выводит предупреждение в консоль браузера.
  • Пользовательские функции проверки – разработчик может написать свои функции для проверки правильности данных. Такие функции можно вызывать внутри компонента и использовать их для отображения сообщений об ошибках или изменения состояния компонента при обнаружении некорректных данных.
  • Сторонние библиотеки – существуют сторонние библиотеки (например yup, joi, zod), которые предоставляют более мощные и гибкие инструменты для проверки данных. Они также обычно предоставляют более удобные способы отображения ошибок.
Материал подготовлен на основе статьи 44 React Frontend Interview Questions.
***

* Facebook принадлежит организации Meta, деятельность которой признана экстремистской и запрещена на территории РФ.

***

Материалы по теме

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ