Как избежать лишней перерисовки компонентов React
Вы когда-то сталкивались с ситуацией, где вы не знаете как оптимизировать отрисовку ваших компонентов и избежать багов, которые генерирует излишние ререндеры?
В этой статье мы поговорим о 5 способах, которые вам помогут избежать лишней перересовки react компонентов.
1. Мемоизация c useMemo() и useCallback()
Мемоизация позволяет вам перерисовывать компонент только в том случае, когда изменяются его пропсы. При помощи этой техники можно избежать ненужных перерисовок и снизить вычислительную нагрузку всего приложения.
React любезно предоставляет два хука для мемоизации:
Эти хуки нейтрализуют лишние перерисовки из-за того что могут отслеживать и возвращать предыдущий результат без каких либо вычислений если входные данные остались преждними.
Когда входные данные меняются происходит новое вычисление, потом в кэше обновляется значение и происходит перерисовка компонента с новыми данными.
useMemo()
Давайте разберем как мы можем использовать хук useMemo().
Давайте напишем простую функцию, которая перемножает два числа:
const multiply = (x, y) => x * y;
Эта функция будет вычислять значение и вызывать перерисовку компонента при каждом своем вызове, вне зависимости от параметров, с которыми она вызвана.
Но, если мы обернем эту функцию в useMemo(), то сможем избежать перерисовки, в ситуации когда параметры не изменяются из-за того, что будет происходить кэширование.
const cachedValue = useMemo(() => multiply(x, y), [x, y])
useCallback()
useCallback() тоже хук, с помощью которого можно мемоизировать значения. Но в отличии от useMemo() он не сохраняет значение, которое возвращает, а сохраняет колбек функцию, которую вызывает.
Например, у нас есть компонент, который хранит в себе список элементов, у которых есть проп onClick
:
import { useCallback } from 'react'; export const List = (items) => { const onClick = useCallback(item => { console.log('Clicked Item : ', item); }, []); return ( <React.Fragment> { items.map(item => ( <ListItem key={item.id} item={item} onClick={onClick} />)); } </React.Fragment> ); }
В этом примере мы кешируем метод onClick
.
2. Использование React Fragments
Если до этого вы сталкивались с react, то наверняка знаете, что необходимо оборачивать компонент в один родительский элемент. Это, конечно, не совсем про перерисовки, но вы знаете что тип родительского контейнера влияет на все время, которое компонент тратит на отрисовку?
В качестве решения вы можете использовать React.Fragment в качестве обертки компонента. Это снизит нагрузку на DOM и, как результат, увеличит скорость отрисовки и уменьшит количество используемой памяти.
const App = () => <React.Fragment><p>Hello<p/><p>World<p/></React.Fragment>;
3. Оптимизация запросов с React Query
Использование хука useEffect для асинхронного получения данных - стандартная практика. Естественно, 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>; }
Библиотека React Query скачивается более 600к раз за неделю и имеет 1.3к звезд на гите.
4. Заменим useState() на useRef()
Использование хука useState() достаточно распространенная практика, для реализации перерисовки компонента при изменении его локального состояния.
Но, иногда нам бывает нужно отследить изменение состояния без перерисовки компонента, в таком случае можно заменить useState() на 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> ); };
Этот пример имеет состояние toggle
, которое тригерит перерисовку компонента при каждом своем изменении. Но counter
сохраняет свое значение, так как это изменяемая ссылка. Поскольку мы используем useRef()
, это вызовет только один рендеринг. Однако, если мы используем useState()
, это вызовет 2 рендеринга.
5. Мемоизированные селекторы с Reselect
Reselect — это сторонняя библиотека React для создания мемоизированных селекторов. Он обычно используется с Redux и обладает замечательными функциями, позволяющими сократить количество ненужных повторных рендерингов.
- Reselect позволяет создавать селекторы, которые вычисляют информацию
- Селекторы Reselect не будут перерисовываться пока их аргументы не поменяются
- Такие селекторы могут использоваться для создания вложенных селекторов
Reselect предоставляет API в виде функции createSelector
. Для лучшего понимания рассмотрим пример, приведенный ниже:
import { createSelector } from 'reselect'; ... const selectValue = createSelector( state => state.values.value1, state => state.values.value2, (value1, value2) => value1 + value2 ) ...
createSelector
принимает на вход 2 селектора и возвращает запомненную версию. Селекторы не будут вычисляться снова, пока значения не будут изменены.
Библиотека Reselect имеет более 2 миллионов еженедельных загрузок NPM и более 18,4 тыс. звезд на GitHub.
На этом сегодня все, надеюсь вы нашли для себя что-то новое в этой статье)
Не забывайте подписываться на телеграм канал и читать посты с хештегом #react!