Повторный рендеринг в React и советы по предотвращению

Привет, ребята. Я Семих. В этой статье я объясню случай повторного рендеринга в ReactJs и дам несколько советов по предотвращению. Я думаю, что многие разработчики React задаются вопросом: "Почему и когда React повторно отображает мой компонент?"

Позвольте мне кратко объяснить повторный рендеринг в React.

Почему?

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

Когда?

Есть некоторые статусы, которые вызывают повторную визуализацию компонентов. Давайте посмотрим на эти статусы. Есть три важных статуса.

  • Когда состояние компонента React изменяется, компонент будет повторно отображаться.
  • Когда компонент перерисовывается, все его дочерние элементы также перерисовываются. Таким образом, направление повторного рендеринга — это «от родителя к дочернему».
  • Когда значение поставщика контекста изменяется, все компоненты, использующие этот контекст, должны выполнять повторную визуализацию. Если какой-либо компонент использует этот контекст, но не использует измененное значение, он все равно выполняет повторную визуализацию.

Позвольте мне исправить некоторые очень неправильно понятые сведения о повторном рендеринге. Изменения свойств не вызывают повторный рендеринг в React.

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

Как мы можем предотвратить повторный рендеринг?

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

Реагировать.memo ()

React.memo — это компонент высокого порядка, который используется для обертывания компонента. Компонент, обернутый React.memo(), перерисовывается только при изменении его свойств. Давайте посмотрим на пример ниже.

...
const [title, setTitle] = useState<String>("");
const [text, setText] = useState<String>("");
const [commentData, setCommentData] = useState<CommentType[]>([]);
...
...
<NewsPage>
  <Image source={'../image.png'} width={600} height={600} />
  <Post title={title} text={text} />                          
  <Comments data={commentData}>
</NewsPage>

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

В этом случае есть проблема. Когда любой пользователь добавляет новый комментарий, мы извлекаем новые комментарии из базы данных и обновляем commentData новыми комментариями с помощью setCommentData. NewsPage и все дочерние элементы будут перерисованы из-за изменения состояния. Изменилось только состояние, связанное с комментарием, состояния, связанные с изображением или публикацией, не изменились, но все же компоненты изображения и публикации были повторно визуализированы.

Мы можем предотвратить эти нежелательные повторные рендеры с помощью React.memo().

export function Image({source,width,height}){
  ...
  ...
  return (
    <>
    ..
    </>
  );  
};

export const MemoizedImage = React.memo(Image);
export function Post({title,text}){
  ...
  ...
  return (
    <>
    ..
    </>
  );  
};

export const MemoizedPost = React.memo(Post);
...
const [title, setTitle] = useState<String>("");
const [text, setText] = useState<String>("");
const [commentData, setCommentData] = useState<CommentType[]>([]);
...
...
<NewsPage>
  <MemoizedImage source={'../image.png'} width={600} height={600} />
  <MemoizedPost title={title} text={text} />                          
  <Comments data={commentData} />
</NewsPage>

Теперь при изменении любого состояния на NewsPage компоненты Image и Post перерисовываются только в том случае, если изменились их реквизиты. Компонент комментариев всегда будет перерисовываться при изменении состояния NewsPage. На самом деле, в этом примере мы предотвращаем повторную визуализацию, вызванную повторной визуализацией родительского элемента и изменением состояния родительского элемента.

использоватьCallback()

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

....
const handleLogout = () => {
  ....
}
....
<App>
  <Logout onClick={handleLogout}/>
</App>

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

....
const handleLogout = useCallback(
  () => cookies.clear('session'),
  [cookies]
)
....
<App>
  <MemoziedLogout onClick={handleLogout}/>
</App>

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

Состав

Есть и другие общие советы по предотвращению повторного рендеринга во время выполнения кода.

1-) Не создавайте компоненты внутри функции рендеринга другого компонента. Это также вызывает повторное монтирование помимо повторного рендеринга.

2-) Если состояние тяжелого компонента используется только в небольшой части этого компонента, создайте небольшой компонент из этой небольшой части и управляйте в нем состоянием.

3-) Если под небольшим компонентом находится медленный компонент, и состояние этого маленького компонента часто меняется (например, компонент прокрутки), передайте медленный компонент как свойство малых компонентов.

const ScrollComponent = ({ children }) => {
  const [value, setValue] = useState({})

  return(
    <div onScroll={ e => setValue(e)}>
      {children}
    </div>
  );
};

const App = () => {
   return (
      <ScrollComponent>
          <SlowComponent />
      </ScrollComponent>
  );
};

Если мы не передаем SlowComponent в качестве дочернего свойства ScrollComponent, SlowComponent повторно отображает каждый запуск прокрутки.

Когда мне следует использовать React.memo?

И последнее, но не менее важное: обертывать каждый компонент React.memo — не очень хороший опыт.

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

Поэтому, если ваш компонент легковесен или обычно рендерится с разными реквизитами, вам не следует оборачивать его в React.memo

Спасибо за прочтение. Ниже приведены несколько полезных статей, которые также вдохновили меня на написание этой статьи.