DEV Community

socaseinpoint
socaseinpoint

Posted on

Оптимизация React компонентов

Image from unsplash by @imwilliamwilliams<br>

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

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

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

1. Мемоизация с помощью использования useMemo() и useCallback() хуков

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

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

  • useMemo()
  • useCallback()

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

useMemo()

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

const multiply = (x,y) => {
  return x*y
}
Enter fullscreen mode Exit fullscreen mode

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

const cachedValue = useMemo(() => multiply(x, y), [x, y])
Enter fullscreen mode Exit fullscreen mode

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

useCallback

useCallback() использует мемоизацию. Отличительная особенность от useMemo() заключается в том, что useCallback() не кэширует результат, вместо этого мемоизирует переданную функцию обратного вызова.

Для примера возьмем компонент с «кликабельным» списком:


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

  return (
    <Listitem={item} onClick={onClick} />
  );
}
Enter fullscreen mode Exit fullscreen mode

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


2. Оптимизация обращений к API с помощью React Query

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

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

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

На момент написания статьи ReactQuery имеет 1000000+ еженедельных скачиваний через npm и больше 28 тысяч звезд на github.


3. Оптимизация селекторов с помощью Reselect

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

Чаще всего используется в связке с библиотекой Redux и по умолчанию включена в официальную библиотеку Redux Toolkit.

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

import { createSelector } from 'reselect' 

const selectValue = createSelector(
  state => state.values.value1,
  state => state.values.value2,
  (value1, value2) => value1 + value2
)
Enter fullscreen mode Exit fullscreen mode

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

Библиотека React Query имеет 4000000+ скачиваний через npm и больше 18 тысяч звезд на github.


4. Заменить useState() на useRef()

useState() — часто используется для условного рендеринга, однако, в случаях, когда изменения состояния не должно вызывать повторный рендер React компонента, тогда лучше использовать useRef() хук.

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'))
Enter fullscreen mode Exit fullscreen mode

Повторный рендер не происходит при изменении переменной counter, поскольку useRef() возвращает изменяемый (мутабельный) объект, который будет сохраняться в течении всего жизненного цикла компонента.

Подробнее можно прочитать в официальной документации React.


5. Использование React Fragments

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

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

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


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

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


Заключение

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

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

Top comments (0)