如何避免重新渲染子组件

时间:2020-08-07 13:35:17

标签: reactjs

我有一个无法解决的渲染问题。 在我的应用程序上,我有一个Posts组件,该组件的状态包含帖子数据数组。

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

在此组件中,我映射了posts状态以为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组件,但是它仍在重新渲染组件,因为当{{1}时作为道具传递给组件的handleLike函数也发生了变化}重新渲染。 也许可以使用Posts钩子来避免这种渲染问题,但是我对这些钩子并不熟悉,我无法让它们按我的意愿工作。

我在这里有什么选择?

您可以在此处找到简化的演示代码:https://codesandbox.io/s/re-render-issue-esm01

7 个答案:

答案 0 :(得分:4)

根据反应的official document,切勿将索引用作键。使用索引作为React组件的键会导致不必要的重新渲染。

让我简要解释一下。密钥是React用来识别DOM元素的唯一东西。如果将项目推送到列表或在中间删除某些内容,会发生什么?它将同时更改其他组件的所有键并重新呈现它们。

要进行更多改进,可以使用React.memo和React.useCallback。您可以在this link

中找到更多详细信息

答案 1 :(得分:2)

我想强调以下几点

当组件的属性或状态更改时,React通过将新返回的元素与先前呈现的元素进行比较来确定是否需要实际的DOM更新。 当它们不相等时,React将更新DOM。

即使 React仅更新已更改的DOM节点,重新渲染仍需要一些时间。在许多情况下,这不是问题,但是如果速度下降很明显,则可以通过覆盖lifecycle函数shouldComponentUpdateReact.memo(如果使用的是函数组件

以您的示例为例,我检查了DOM,并观察到在实际DOM 中只有相应的like count正在更新。

DOM update

要解决性能问题(重新渲染时明显变慢),可以使用Jayshree Wagh's answer中建议的方法

// 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);

请参见good example for useCallback

如果在实际代码中使用index,请避免将key用作{{1}}。

答案 2 :(得分:1)

您可以做这样的事情

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

当函数返回true时,将不会重新渲染组件

答案 3 :(得分:0)

您的处理程序将在每个渲染器上重新计算,从而使React.memo(Post)无用。

请使用useCallback钩子 https://en.reactjs.org/docs/hooks-reference.html#usecallback

答案 4 :(得分:0)

您应该在useCallback处理程序上使用handleLike。然后,状态设置器应使用回调而不是可靠的值,例如:

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

答案 5 :(得分:-1)

我不知道如何使用React Hook做到这一点,但是您可以将Post组件从功能组件转换为类组件,然后使用shouldComponentUpdate()选择使组件重新呈现的方式。

答案 6 :(得分:-1)

我会在全局状态下添加一个类似InitializeApp的东西以及一个可以在组件上调用的ActionCreator,或者可以使用React.memo“ https://reactjs.org/docs/react-api.html#reactmemo”