Оптимизация React компонентов
Tags: react, javascript, webdev, beginners
Published: 2022-07-11 14:31:18


![Image from unsplash by @imwilliamwilliams
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb8ieo2lxi6x2kzn3nrp.jpg)

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

В этой статье, мы разберем 5 практических способов оптимизации производительности:

* Мемоизация с помощью использования `useMemo()` и `UseCallback()` хуков
* Оптимизация обращений к API с помощью React Query
* Оптимизация селекторов с помощью Reselect
* Заменить `useState()` на `useRef()`
* Использование React Fragments

---

## 1. Мемоизация с помощью использования [`useMemo()`](https://ru.reactjs.org/docs/hooks-reference.html#usememo) и [`useCallback()`](https://ru.reactjs.org/docs/hooks-reference.html#usecallback) хуков

[Мемоизация](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F) позволяет вашему коду перерисовывать компоненты, только если вы изменили «[пропсы](https://ru.reactjs.org/docs/components-and-props.html)». C помощью этой техники разработчики могут избавиться от ненужных рендеров и уменьшить вычеслительную нагрузку в приложении.

Из коробки React предлагает два способа мемоизации:

* `useMemo()`
* `useCallback()`

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

**useMemo()**

Разберем механизм работы useMemo, на примере умножения двух чисел:

```js
const multiply = (x,y) => {
  return x*y
}
```

`multiply` — перерасчитывает результат на каждый вызов функции, следовательно компонент каждый раз будет перерисован, не смотря на то что входные параметры функции не изменились. Но если мы используем хук `useMemo()`, то сможем избежать излишних рендеров, если входные параметры не изменятся и результат вызова функции будет в кэше.

```js
const cachedValue = useMemo(() => multiply(x, y), [x, y])
```

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

**useCallback**

`useCallback()` использует мемоизацию. Отличительная особенность от `useMemo()` заключается в том, что `useCallback()` не кэширует результат, вместо этого мемоизирует переданную [функцию обратного вызова](https://developer.mozilla.org/ru/docs/Glossary/Callback_function).

Для примера возьмем компонент с «[кликабельным](https://ru.reactjs.org/docs/components-and-props.html)» списком:

```js

import { useCallback } from 'react';
export const ClickableListWithMemoization = ({ term }) => {
  const onClick = useCallback(event => {
    console.log('Clicked Item : ', event.currentTarget);
  }, [item]);

  return (
    <Listitem={item} onClick={onClick} />
  );
}
```

В вышеизложенном примере `useCallback()` мемоизирует функцию обратного вызова `onClick`, переданную обработчику событий, поэтому компонент не будет вызывать новые рендеры при клике на один и тот же элемент списка.

---

## 2. Оптимизация обращений к API с помощью [React Query](https://tanstack.com/query/v4/?from=reactQueryV3&original=https://react-query-v3.tanstack.com/)

`useEffect()` часто используется, для вызова асинхронных запросов к API, однако `useEffect()` делает запрос на каждый рендер компонента и чаще всего дынный вызов вернет те же данные.

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

```js
import React from 'react'
import {useQuery} from 'react-query'
import axios from 'axios'

async function fetchArticles(){
  const {data} = await axios.get(URL)    
  return data
}

export const Articles = () => {
  const {data, error, isError, isLoading } = useQuery('articles', fetchArticles)
 
  if(isLoading){
    return <div>Loading...</div>
  }

  if(isError){
    return <div>Error! {error.message}</div>
  }

  return(
    <div>
      ...
    </div>
  )
}
```

На момент написания статьи ReactQuery имеет 1000000+ еженедельных скачиваний через [npm](https://www.npmjs.com/package/react-query) и больше 28 тысяч звезд на [github](https://github.com/TanStack/query).

---

## 3. Оптимизация селекторов с помощью [Reselect](https://github.com/reduxjs/reselect)

Reselect — сторонняя библиотека для создания мемоизированных селекторов, которая позволяет уменьшить количество повторных рендеров React компонентов.

Чаще всего используется в связке с библиотекой Redux и по умолчанию включена в официальную библиотеку [Redux Toolkit](https://redux-toolkit.js.org/).

Создать селектор с помощью Reselect, можно с помощью функции `createSelector`:

```js
import { createSelector } from 'reselect' 

const selectValue = createSelector(
  state => state.values.value1,
  state => state.values.value2,
  (value1, value2) => value1 + value2
)
```

В примере выше функция `createSelector` создает селектор, который не будет вычислять новое значение, пока входные данные не изменятся.

Библиотека React Query имеет 4000000+ скачиваний через [npm](https://www.npmjs.com/package/reselect) и больше 18 тысяч звезд на [github](https://github.com/reduxjs/reselect).

---

## 4. Заменить [`useState()`](https://ru.reactjs.org/docs/hooks-reference.html#usestate) на [`useRef()`](https://ru.reactjs.org/docs/hooks-reference.html#useref)

`useState()` — часто используется для [условного рендеринга](https://reactjs.org/docs/conditional-rendering.html), однако, в случаях, когда изменения состояния не должно вызывать повторный рендер React компонента, тогда лучше использовать `useRef()` хук.

```js
const App = () => {
  const [toggle, setToggle] = React.useState(false)
  const counter = React.useRef(0)
 
  console.log(counter.current++)
 
  return (
    <button onClick={() => setToggle(toggle => !toggle)}> 
      Click 
    </button>
  )
}

ReactDOM.render(<React.StrictMode><App /></React.StrictMode>, document.getElementById('mydiv'))
```

Повторный рендер не происходит при изменении переменной `counter`, поскольку `useRef()` возвращает изменяемый ([мутабельный](https://developer.mozilla.org/en-US/docs/Glossary/Mutable)) объект, который будет сохраняться в течении всего жизненного цикла компонента.

Подробнее можно прочитать в [официальной документации React](https://ru.reactjs.org/docs/hooks-reference.html#useref).

---

## 5. Использование [React Fragments](https://ru.reactjs.org/docs/react-api.html#reactfragment)

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

```js
render() {
  return (
    <React.Fragment>
      Какой-то текст.
      <h2>Заголовок</h2>
    </React.Fragment>
  );
}
```

Используйте сокращенный синтаксис `<></>` для создания фрагментов.

```js

render() {
  return (
    <>
      Какой-то текст.
      <h2>Заголовок</h2>
    </>
  );
}
```

React Fragments не создает DOM элемент, что позволяет ускорить рендеринг и сэкономить память.

---

## Заключение

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

Спасибо за прочтение! Напишите, если статья была полезной, любая критика приветствуется.