react重复渲染 for ItemList(列表项)

先看效果,左边是badCase(一般的写法),中间是用reducer来状态管理,右边是用memo和callback

可以看到第一种每一个子项都被渲染了(1+6+5)次,2,3是我们希望的,其中2的父组件没有被重复渲染,是最优的方法

React 基本的渲染逻辑

我们都知道,React 是通过虚拟 DOM 树来进行渲染的。当我们每次调用 setState 时,都会引起整个虚拟 DOM 树的重新构建,然后通过 Fiber 算法对比得出需要渲染的部分。

即使不使用 memouseCallback,React 也有一个基本的优化逻辑:

React 默认的优化逻辑虽然 React 渲染是从根节点开始的,但在遍历过程中,如果发现节点本身以及祖先节点没有更新,而是其子树发生了更新,那么该节点也不会被重新渲染。

父组件和子组件的渲染关系

  1. 父组件的重复渲染会导致子组件全部重复渲染
  2. 单一子组件的渲染在一般情况下不会引起父组件的渲染

在处理列表项时,我们希望列表项是独立的,其数据变化不会引起其他列表项的变化。然而,通常情况下会出现父组件被重复渲染的情况,这会导致所有子组件也被重新渲染。

不使用memo和callback,避免父组件重复渲染

为什么我要先不说他俩,因为上图的第三个状态用的是useMemo和useCallback,他有达到我们的期望吗(不重复渲染其他子项),有的,但是他是最好的吗,可以看到父组件依然被重复渲染了
(当然父组件一般只有一个,渲染就渲染了,不会特别消耗性能)。
我还是想说更核心的东西,怎么避免子项改变时候,父亲组件重新渲染,为什么呢,原因在useState
 const [items, setItems] = useState([
    { id: 1, renderTime: 0, count: 0 },
    { id: 2, renderTime: 0, count: 0 },
    { id: 3, renderTime: 0, count: 0 },
  ]);

为什么用useState本质上是要去改变状态,但是会有副作用

每次对item的更改都会引起state的变化

而state变化不会有React 默认的优化逻辑(子项变化,父项不重新渲染),就会导致react默认的优化失效

我们可以通过以下方式避免父组件的重复渲染:

  1. 使用 useReducer 代替 useState:当我们处理后端返回的数据列表时,使用 useState 保存列表项会导致父组件的重新渲染。通过 useReducer 可以避免这种情况,因为 useReducer 可以更细粒度地控制状态更新,从而避免不必要的重新渲染。

import React, { useReducer } from "react";
let fatherRenderTime = 0;

const itemReducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
};

const Item = ({ item }) => {
  const [state, dispatch] = useReducer(itemReducer, item);

  return (
    <div>
      <div>itemRenderTime: {state.renderTime++}</div>
      <div>Count: {state.count}</div>
      <button onClick={() => dispatch({ type: "increment" })}>
        Increment Count
      </button>
    </div>
  );
};

export function ReducerSolution() {
  const items = [
    { id: 1, renderTime: 0, count: 0 },
    { id: 2, renderTime: 0, count: 0 },
    { id: 3, renderTime: 0, count: 0 },
  ];

  return (
    <div>
      <div>
        <div>fatherRenderTime: {fatherRenderTime++}</div>
        {items.map((item) => (
          <Item key={item.id} item={item} />
        ))}
      </div>
    </div>
  );
}

export default ReducerSolution;

使用 useMemouseCallback

在一些复杂的项目中,useMemouseCallback 可以减少代码改动,同时达到优化渲染的效果。

  • useMemouseCallback 的核心点:避免父组件的渲染导致不应该被重复渲染的子组件被重复渲染。
  • 依赖项的作用:当依赖项没有发生变化时,React 会使用缓存,否则会重新渲染。这解释了为什么 useCallbackuseMemo 需要一起使用,因为依赖项中通常包含需要调用的函数。如果不使用 useCallback,函数会发生变化,导致依赖项变化,从而使 useMemo 失效。

import React, { useState, memo, useCallback } from "react";

let fatherRenderTime = 0;

const Item = memo(({ item, onIncrement, add }) => {
  return (
    <div>
      <div>itemRenderTime: {item.renderTime++}</div>
      <div>Count: {item.count}</div>
      <button onClick={() => onIncrement(item.id)}>Increment Count</button>
      {/* <button onClick={() => add(item.id)}>Increment Count</button> */}
    </div>
  );
});

export function MemoAndCallBackSolution() {
  const [items, setItems] = useState([
    { id: 1, renderTime: 0, count: 0 },
    { id: 2, renderTime: 0, count: 0 },
    { id: 3, renderTime: 0, count: 0 },
  ]);

  const incrementCount = useCallback((id) => {
    setItems((prevItems) =>
      prevItems.map((item) =>
        item.id === id ? { ...item, count: item.count + 1 } : item
      )
    );
  }, []);
  const add = (id) => {
    setItems((prevItems) =>
      prevItems.map((item) =>
        item.id === id ? { ...item, count: item.count + 1 } : item
      )
    );
  };
  return (
    <div>
      <div>fatherRenderTime: {fatherRenderTime++}</div>
      {items.map((item) => (
        <Item
          key={item.id}
          item={item}
          onIncrement={incrementCount}
          // add={add}
        />
      ))}
    </div>
  );
}

export default MemoAndCallBackSolution;
s

示例流程

father组件渲染   ---1----->     son组件渲染   -----2--->   father组件渲染  

  • 1 是正常现象,但可以通过 memouseCallback 来优化(实际上也会重复渲染,但使用的是缓存,没有增加性能消耗)。优化的条件是依赖项没有发生变化。
  • 2 是 React 自身的优化逻辑,一般情况下 React 组件都会遵循。我们在列表处理时容易重复渲染的主要原因是使用了 useState 去保存后端传递的列表项。

结论

通过这篇文章,我个人对 useMemouseCallback 有了更深入的了解,也解释了一些问题(为什么有时候加了和没加一样,有时候单独用不起效果)。

建议:对于复杂的列表项操作,可以考虑使用 useReducer 代替 useState。一般情况下,父组件不会把子组件的状态全部包揽在一个 state 里,但列表项是个例外。

希望这篇文章对你有所帮助。

资料来源

#我的简历长这样##我的求职思考#
全部评论
学到了
1 回复 分享
发布于 06-27 13:35 北京
学到了
1 回复 分享
发布于 06-28 13:07 浙江

相关推荐

点赞 评论 收藏
分享
2 6 评论
分享
牛客网
牛客企业服务