性能:React 实战优化技巧

React
41
0
0
2024-11-18

🌿 性能优化的主要点: 1️⃣ 减少 DOM 的渲染频次 2️⃣ 减少 DOM 的渲染范围 3️⃣ 非必要的内容延后处理

React 在组件触发刷新的时候,会深度遍历所有子组件。➡️ 父组件刷新,子组件跟着刷新。

避免不必要的组件重新渲染,是提高程序性能的重要方式之一。

缓存方式
React.memo


const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。

即使一个组件被记忆化了,当它自身的状态/ context 发生变化时,它仍然会重新渲染。memoization 只与从父组件传递给组件的 props 有关。

const MyComponent = memo(({name}) => {
    const [age, setAge] = useState();
    return (<>
        <input type="text" value={age} onChange={(e) => setAge(e.target.value)}/>
				{Date.now()}
    </>)
});

props name 改变,及 ② 自身 state age 改变,组件都会重新渲染。

当使用 memo 时,只要任何一个 prop 与先前的值不等的话,组件就会重新渲染。这意味着 React 会使用 Object.is 比较组件中的每个 prop 与其先前的值。

Object.is(3, 3) 		// true
Object.is({}, {})		// false

Object.is()==运算符并不等价== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型)。

"" == false			     // true
Object.is("", false)     // false

Object.is()=== 运算符也不等价Object.is()=== 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。

+0 === -0			 // true
0 === NaN		     // false
Object.is(-0, +0) 	 // false
Object.is(0, NaN)    // false

当props是对象或函数时,Object.is() 永远不相等。为了解决这个问题,React 中引入了 useMemouseCallback

👉 👉 如果 props 是一个对象,可以使用 useMemo 避免父组件每次都重新创建该对象。

useMemo


const cachedValue = useMemo(calculateValue, dependencies)

在每次重新渲染的时候能够缓存计算的结果。如果依赖项没有发生改变,它将返回上次缓存的值;否则将再次调用 calculateValue,并返回最新结果。

function Page() {
  const [name, setName] = useState('ligang');
  const [age, setAge] = useState(34);

  // 避免因 `Object.is` 不相等,导致的重新渲染
  const person = useMemo(
    () => ({ name, age }),
    [name, age]
  );

  return <MyComponent person={person} />;
}

const MyComponent = memo(({person}) => {
  // ...
});

👉👉 如果 props 是一个函数,可以将函数包装到 useCallback 避免父组件每次都重新创建该函数。

useCallback
const cachedFn = useCallback(fn, dependencies)

在多次渲染中缓存函数。在初次渲染时,useCallback 返回传入的 fn 函数;在之后的渲染中,如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn

export default () => {
    const [name, setName] = useState('');
  
  	// 不依赖任何state
    const handleSubmit = useCallback(() => {
        // ...
    }, []);
  
    return MyComponent onSubmit={handleSubmit}/>;
}

const MyComponent = memo(({onSubmit}) => {
  // ...
});

【示例】

不使用 useCallback,每次父组件 name 改变,子组件MyComponent 都重新渲染(即便使用 memo 进行了包裹)

使用 useCallback,每次父组件 name 改变,子组件MyComponent 不再重新渲染( memo 生效)

精准控制
key

key 是一个特殊的字符串属性,用于帮助 React 识别哪些元素改变了。

在列表渲染时 key 属性可以用于识别 React 的 diff 算法哪些列表项已更改,通过复用具有相同 key 的组件实例,React可以减少了不必要的DOM操作&重新渲染,从而提升界面更新的效率。

为了正确使用key属性,确保所提供的key是稳定、唯一且可预测的。

虚拟化

最常见的虚拟列表。仅渲染可见部分,而不是全部内容,实现性能的提升。

以 ahooks useVirtualList 为例:

export default () => {
  const containerRef = useRef(null);
  const wrapperRef = useRef(null);

  const originalList = useMemo(() => Array.from(Array(99999).keys()), []);

  // 配置虚拟滚动
  const [list] = useVirtualList(originalList, {
    containerTarget: containerRef, 	 // 外面容器
    wrapperTarget: wrapperRef,		 // 内部容器
    itemHeight: 60,					 // 行高度
    overscan: 10,  					 // 视区上、下额外展示的 DOM 节点数量
  });
  return (
    <>
      {/* 指定可视区域高度 */}
      <div ref={containerRef} style={{ height: '300px', overflow: 'auto', border: '1px solid' }}>
        <div ref={wrapperRef}>
          {list.map((ele) => (
            <div
              style={{
                height: 52,
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                border: '1px solid #e8e8e8',
                marginBottom: 8,
              }}
              key={ele.index}
            >
              Row: {ele.data}
            </div>
          ))}
        </div>
      </div>
    </>
  );
};
代码分割&懒加载
代码分隔(Code splitting)是一种前端技术,用于将应用程序的代码拆分成较小的块,以优化加载性能和减少初始加载时间。它可以帮助减少初始下载量,并根据需要动态加载所需的代码。

在需要时才加载组件,从而减少初始加载时间并提高性能。

动态导入(Dynamic Imports)


export default () => {
  const [module, setModule] = useState(null);
  
  useEffect(() => {
    import('./MyModule').then((mod) => setModule(mod));
  }, []);

  return module ? <module.default /> : <div>Loading...</div>;
}
React.lazy()Suspense


const MyModule = React.lazy(() => import('./MyModule'));

export default () => (
  <Suspense fallback={<div>Loading...</div>}>
    <MyModule />
  </Suspense>
);