Как избежать повторного рендеринга дочерних компонентов - PullRequest
3 голосов
/ 07 августа

У меня проблема с отображением, которую я не могу исправить. В моем приложении у меня есть компонент Posts, состояние которого содержит массив данных сообщений.

[
  { id: "P01", title: "First post", likeCount: 0 },
  { id: "P02", title: "Second post", likeCount: 0 }
]

В этом компоненте я отображаю состояние posts, чтобы вернуть компонент Post для каждое сообщение внутри состояния сообщений.

Компонент сообщений

export default function Posts() {
  const [posts, setPosts] = useState(null);

  const fetchPosts = () => {
    setPosts([
      { id: "P01", title: "First post", likeCount: 0 },
      { id: "P02", title: "Second post", likeCount: 0 }
    ]);
  };

  const handleLike = (id) => {
    setPosts(
      posts.map((post) =>
        post.id === id ? { ...post, likeCount: post.likeCount + 1 } : post
      )
    );
  };

  return (
    <div>
      <button onClick={fetchPosts}>Fetch posts</button>
      {posts !== null ? (
        posts.map((post, index) => {
          return <Post key={index} post={post} handleLike={handleLike} />;
        })
      ) : (
        <p>No post to dipslay</p>
      )}
    </div>
  );
}

Компонент сообщения

function Post({ post, handleLike }) {
  console.count("Renders for: " + post.id);
  return (
    <div>
      <h1>{post.title}</h1>
      <p>Like count: {post.likeCount}</p>
      <button onClick={() => handleLike(post.id)}>Like</button>
    </div>
  );
}


export default React.memo(Post);

Проблема в том, что, когда мне нравится Post, все другие Post компоненты повторно визуализируются. Я добавил console.count() к дочернему компоненту Post, чтобы показать это в действии (см. Ниже ссылку на песочницу). Из-за этого у меня проблема с производительностью, потому что в моем приложении у меня есть много сообщений для отображения с большим количеством дочерних компонентов.

Я пытался обернуть компонент Post, используя React.memo, но он все еще выполняет повторный рендеринг компонента, потому что функция handleLike, переданная компоненту в качестве свойств, также изменилась при повторном рендеринге Posts. Возможно, можно избежать этой проблемы с рендерингом с помощью хуков useMemo / useCallback, но я не знаком с ними и не могу заставить их работать так, как я хочу.

Какие у меня здесь варианты?

Здесь вы можете найти упрощенный демонстрационный код: https://codesandbox.io/s/re-render-issue-esm01

Ответы [ 7 ]

4 голосов
/ 07 августа

Никогда не используйте индексы в качестве ключа в соответствии с официальным документом . Использование индекса в качестве ключа в компоненте реакции приведет к нежелательному повторному рендерингу.

Позвольте мне кратко объяснить. Ключ - это единственное, что React использует для идентификации элементов DOM. Что произойдет, если вы добавите sh элемент в список или удалите что-то посередине? Он также изменит все ключи других компонентов и повторно отобразит их.

Для большего улучшения вы можете использовать React.memo и React.useCallback. Более подробную информацию можно найти в по этой ссылке

2 голосов
/ 07 августа

Я хотел бы выделить следующие моменты прежде всего.

Когда компонент props или изменение состояния , React решает, необходимо ли фактическое обновление DOM, сравнивая новые возвращенный элемент с ранее визуализированным. Если они не равны, React обновит DOM.

Даже если React обновляет только измененные узлы DOM , повторная визуализация все равно занимает некоторое время. Во многих случаях это не проблема, но если замедление заметно, вы можете ускорить все это, переопределив функцию lifecycle shouldComponentUpdate (React.memo, если вы используете функциональный компонент )

Переходя к вашему примеру, я проверил DOM и заметил, что только соответствующий like count обновляется в фактическом DOM .

DOM update

To combat with performance issue (noticeable slowdown in re-rendering), you can use approach suggested in Jayshree Wagh ответ как показано ниже

// Posts.js
const handleLike = useCallback(id => {
  setPosts(posts => posts.map(
      post => post.id === id ? ({ ...post, likeCount: post.likeCount + 1 }) : post
    )
  );
}, []);

// Post.js
export default React.memo(Post, (prevProps, nextProps) => prevProps.post === nextProps.post);

См. Хороший пример Callback .

Избегайте использования index как key, если вы используя его в реальном коде.

1 голос
/ 07 августа

Вы можете сделать что-то вроде этого

const Post = React.memo(
  props => {...},
 (prevProps, nextProps) => prevProps.post === nextProps.post
);

когда функция возвращает true, компонент не будет повторно визуализирован

0 голосов
/ 07 августа

Я бы добавил что-то вроде InitializeApp в глобальном состоянии и ActionCreator, который вы можете вызвать в своем компоненте, или вы можете использовать React.memo «https://reactjs.org/docs/react-api.html#reactmemo».

0 голосов
/ 07 августа

Вы должны использовать useCallback в своем обработчике handleLike. Затем установщик состояния должен использовать обратный вызов вместо надежного значения, например:

const handleLike = useCallback((id) => {
  setPosts(
    posts=>{
      return posts && posts.map((post) =>
        post.id === id ? { ...post, likeCount: post.likeCount + 1 } : post,
      )
    },
  );
},[setPosts]);
0 голосов
/ 07 августа

Ваши обработчики пересчитываются на каждом рендере, что делает React.memo(Post) бесполезным.

Используйте useCallback hook https://en.reactjs.org/docs/hooks-reference.html#usecallback

0 голосов
/ 07 августа

Я не знаю, как это сделать с помощью React Hook, но вы можете преобразовать компонент Post из функционального компонента в компонент класса, а затем использовать shouldComponentUpdate(), чтобы выбрать, что заставит ваш компонент повторно визуализироваться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...