DevGang
Авторизоваться

React: оптимизация компонентов с помощью React.memo, useMemo и useCallback

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

Давайте рассмотрим пример:

const ListPage = ({data, title}) => (  
  <div>  
    <Header title={title}/>  
    <List listItems={data}/>  
  </div>  
)  

В этом примере любые изменения в data вызовут ListPage повторную визуализацию всех его дочерних компонентов, включая компонент Header, даже если title не изменились. Header будет отображать один и тот же результат с одним и тем же реквизитом, поэтому любой рендеринг с одним и тем же реквизитом не нужен. В этом случае это, вероятно, не имеет большого значения, но если бы <Header/> выполнял какое-то дорогостоящее вычисление каждый раз, когда он рендерится, мы бы хотели убедиться, что он визуализируется только тогда, когда это необходимо.

‍К счастью, есть несколько способов оптимизации для этого сценария.

‍При использовании компонентов на основе классов Pure Component возвращает последнее отрисованное значение, если переданные в реквизитах совпадают. Существует также функция shouldComponentUpdate для более тонкой настройки управления. При использовании функциональных компонентов React предоставляет три метода оптимизации, которые будут рассмотрены в этой статье:React.memo,useMemo и useCallback.

React.Memo

React.memo это компонент более высокого порядка, который запоминает результат компонента функции. Если компонент возвращает один и тот же результат с одинаковыми реквизитами, его обертывание в memo может привести к повышению производительности. Возьмем наш предыдущий пример <Header/>.

‍Скажем, это выглядит примерно так:

const Header = ({title}) => <h1>{title}</h1>

export default Header;

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

const Header = ({title}) => <h1>{title}</h1>

export default React.memo(Header);  

Теперь, когда Header визуализируется, он будет делать неглубокое сравнение с его реквизитами. Если эти реквизиты одинаковы, он пропустит отрисовку и вместо этого вернет свое последнее отрисованное значение.

‍Краткое замечание об использовании memo и реагировании средств разработки. На момент написания этой статьи, оберните ваш компонент вот так...

const Header = React.memo(({title}) => <h1>{title}</h1>));

export default Header;  

...это приведет к тому, что ваш компонент будет отображаться как Unknown в react dev tools. Чтобы исправить это, оберните свой компонент memo после его определения, как мы делали это ранее:  

const Header = ({title}) => <h1>{title}</h1>;

export default React.memo(Header);  

useMemo

useMemo позволяет запомнить результаты функции и будет возвращать этот результат до тех пор, пока массив зависимостей не изменится.

‍Например:

const widgetList = useMemo(  
  () => widgets.map(  
    w => ({  
      ...w,  
      totalPrice: someComplexFunction(w.price),  
      estimatedDeliveryDate: someOtherComplexFunction(w.warehouseAddress)  
    }),  
  ),  
  [widgets],  
);  

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

‍Использование useMemoзапомнит результат, поэтому, если widgetsон не изменился с момента последнего рендеринга компонента, он пропустит вызов функции и вернет то, что он получил последним.

useCallback

useCallback может предотвратить ненужную визуализацию между родительским и дочерним компонентами.

‍Возьмем такой пример:

const Parent = () => {  
  const [showExtraDetails, setShowExtraDetails] = useState(false);  
  return (  
    [...]  
    <Child onClick={() => { showData(showExtraDetails); }/>  
    [...]  
  );  
}  

Этот компонент будет вызывать повторную визуализацию Child элемента каждый раз, когда это делает Parent, даже если Child элемент является PureComponent или завернут в React.memo, потому что при каждом рендеринге onClick будет отличаться. useCallback может справиться с этой ситуацией так:  

const Parent = () => {  
  const [showExtraDetails, setShowExtraDetails] = useState(false);  
  const handleClick = useCallback(  
    () => {  
    showData(showExtraDetails);  
  },  
  [showExtraDetails],  
);  
  return (  
    [...]  
    <Child onClick={() => {handleClick}/>  
    [...]  
  );  
}  

Теперь handleClick будет иметь то же значение, пока showExtraDetails не изменится, что позволит сократить количество раз, когда Child рендерится.

Что нужно учитывать при оптимизации в React

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

Источник:

#JavaScript #React #Optimisation
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу