React 开发中,我们经常需要优化组件的性能,以提高应用程序的响应速度和效率。memo、useMemo 和 useCallback 是 React 中用于性能优化的重要工具。它们可以帮助我们避免不必要的重新渲染和函数重复创建,从而提升应用的性能。

React && memo、useMemo、useCallback

一、memo

memo 是 React 提供的一个高阶组件(Higher-Order Component),用于优化组件的渲染。当组件的 props 没有发生变化时,memo 会缓存组件的渲染结果,并在下一次渲染时直接返回缓存的结果,从而避免不必要的重新渲染。

memo 的使用非常简单,只需将要进行优化的组件包裹在 memo 组件中即可。例如:

import React, { memo } from 'react'; const MyComponent = memo((props) => { // 组件的渲染逻辑 }); export default MyComponent;

memo 组件会自动对组件的 props 进行浅比较,如果 props 没有发生变化,memo 会直接返回上一次渲染的结果,否则会重新渲染组件。

memo 的使用场景通常是在组件的 props 变化频率较低且渲染开销较大的情况下,可以有效地减少不必要的渲染,提升性能。

二、useMemo

useMemo 是 React 提供的一个 Hook,用于缓存计算结果。它接收一个计算函数和依赖项数组作为参数,并返回计算结果。当依赖项数组发生变化时,useMemo 会重新计算结果并返回,否则会直接返回上一次的缓存结果。

useMemo 的作用是避免重复计算昂贵的操作,从而提高应用程序的性能。它适用于那些计算量较大且结果不经常变化的情况。

使用 useMemo 的示例代码如下:

import React, { useMemo } from 'react'; const MyComponent = () => { const result = useMemo(() => { // 计算逻辑 return someValue; }, [dependency1, dependency2]); // 组件的渲染逻辑 };

在上面的例子中,当 dependency1 或 dependency2 发生变化时,useMemo 会重新计算结果并返回。否则,它会直接返回上一次的缓存结果。

使用 useMemo 可以有效地避免重复计算,提高应用程序的性能。

三、useCallback

useCallback 是 React 提供的另一个 Hook,用于缓存函数。它接收一个函数和依赖项数组作为参数,并返回一个缓存的函数。当依赖项数组发生变化时,useCallback 会返回一个新的函数,否则会返回上一次的缓存函数。

useCallback 的作用是避免不必要的函数重复创建,特别是在将函数作为 props 传递给子组件时,可以提高性能。

使用 useCallback 的示例代码如下:

import React, { useCallback } from 'react'; const MyComponent = () => { const handleClick = useCallback(() => { // 处理点击事件的逻辑 }, [dependency1, dependency2]); // 组件的渲染逻辑 };

在上面的例子中,当 dependency1 或 dependency2 发生变化时,useCallback 会返回一个新的函数。否则,它会返回上一次的缓存函数。

使用 useCallback 可以避免不必要的函数重复创建,提高应用程序的性能。

四、使用 memo、useMemo 和 useCallback 综合实践

memouseMemouseCallback 可以结合使用,以进一步优化组件的性能。

完整示例代码如下:

  • 子组件 Com1.tsx:
// src/components/com1.tsx import {FC, memo} from "react"; const Com: FC<{ count?: number, test: ()=>void }> = ({ count,test }) => { console.log('re-render children Com1') return <div> Com1 -- {count || 0} <br/> <button onClick={test}>App parent test function</button> </div>; } export default memo(Com);
  • 子组件 Com2.tsx:
// src/components/com2.tsx import {memo} from "react"; const Com = () => { console.log('re-render children Com2') return <div>Com2</div>; } export default memo(Com);
  • 父组件 App.tsx:
// src/App.tsx import './App.css'; import Com1 from "./components/com1"; import Com2 from "./components/com2"; import {useCallback, useEffect, useMemo, useState} from "react"; const App = () => { const [count, setCount] = useState(0); console.log('re-render parent App') // function test() { // console.log('test run') // } // const test = useMemo(()=> { // return () => { // console.log('test run') // } // }, []) const test = useCallback(()=> { console.log('test run') setCount(1000) }, []) useEffect(()=>{ setInterval(()=>{ // setCount(count + 1) // setCount((val)=>{ // return val + 1 // }) }, 1000) }, []) return ( <div className="content"> <h1>memo、useMemo、useCallback</h1> <div style={{padding: '10px 0'}}> <button onClick={()=>{ setCount(100) }}>{count}++</button> </div> <br/> <Com1 { ...{test} } /> <br/> <Com2 /> </div> ); }; export default App;

五、总结

综上所述,memouseMemouseCallback 是 React 中用于性能优化的重要工具,但单独使用它们并不能保证子组件不受父组件的影响。为了确保子组件的独立性,可以考虑使用 React.memo 包裹子组件。

1. memo:

  • 父组件重新渲染,没有被 memo 包裹的子组件也会重新渲染;
  • memo 包裹的组件只有在 props 改变后,才会重新渲染;
  • memo 只会对新旧 props 做浅比较,所以对于引用类型的数据如果发生了更改,需要返回一个新的地址;
  • memo 并不是用的越多越好,因为缓存本身也是需要开销的。如果每一个组件都用 memo 去包裹一下,那么对浏览器的开销就会很大,本末倒置了;
  • 项目中可以针对刷新频率高的组件,根据实际情况,使用 memo 进行优化。

2. useMemo:

  • useMemo 是对计算的结果进行缓存,当缓存结果不变时,会使用缓存结果;
  • useMemo 并不是用的越多越好,对于耗时长、性能开销大的地方,可以使用 useMemo 来优化,但大多数情况下,计算结果的开销还没有使用 useMemo 的开销大,应视情况而定;
  • 当父组件传了一个引用类型的结果 result 给子组件,且子组件用 memo 包裹时,需要使用 useMemo 对 result 进行缓存,因为 memo 只对 props 做浅比较,当父组件重新渲染时,会重新在内存中开辟一个地址赋值给 result,此时地址发生改变,子组件会重新渲染。

3. useCallback:

  • useCallbackuseMemo 类似,只不过是对函数进行缓存;
  • useCallback 可以单独使用,但是单独使用的使用对性能优化并没有实质的提升,且父组件此时重新渲染,子组件同样会渲染;
  • useCallback 需要配合 memo 一起使用,这样当父组件重新渲染时,缓存的函数的地址不会发生改变,memo 浅比较会认为 props 没有改变,因此子组件不会重新渲染。

欢迎访问:天问博客