React学习———React.memo、useMemo和useCallback
React.memo
React.memo是React提供的一个高阶组件,用于优化函数组件的性能,它通过记忆组件的渲染结果,避免在父组件重新渲染时,子组件不必要的重新渲染
React.memo会对组件的props进行浅比较,如果props没有变化,则组件不会重新渲染
基本用法
import React from 'react'
const MyComponent = (props) => {console.log('组件渲染了')return <div>{props.value}</div>
}
export default React.memo(MyComponent)
工作原理
- 默认浅比较:React.memo会对传递给组件的props进行浅比较,如果props没有变化,则跳过重新渲染
- 自定义比较函数(可选):如果需要更复杂的比较逻辑,可以通过第二个参数传入自定义比较函数
自定义比较函数
const MyComponent = (props) => {console.log('组件渲染了')return <div>{props.value}</div>
}
const areEqual = (prevProps, nextProps) => {// 自定义比较逻辑return prevProps.value === nextProps.value
}
export default React.memo(MyComponent, areEqual)
使用场景
- 纯函数组件:当组件的渲染结果完全依赖于props,且没有内部状态或副作用时,使用React.memo可以有效避免不必要的渲染
- 频繁渲染的组件:在父组件频繁更新,但子组件的props变化较少的情况下,使用React.memo可以显著提升性能
- 大型列表或复杂组件:对于渲染成本较高的组件,使用React.memo可以减少渲染次数,提升应用的整体性能
注意事项
- 浅比较的局限性:React.memo默认使用浅比较。如果props是复杂的嵌套对象或数组,可能需要自定义比较函数
- 状态或上下文变化:React.memo只对props的变化敏感,如果组件依赖于状态(state)或上下文(context),这些变化仍会触发重新渲染
- 过度优化:不要滥用React.memo。因为浅比较和自定义比较函数本身也会带来一定的性能开销
useMemo
useMemo是一个React Hook,用于缓存计算结果,避免在每次渲染时重复执行耗时的计算
特点
- 用于性能优化,减少不必要的计算
- 只有依赖项发生变化时,才会重新计算值
- 返回缓存的计算结果
基本用法
import React, { useMemo, useState } from 'react';
function ExpensiveCalculation(num){console.log('Calculating...');return num * 2
}
function App(){const [count,setCount] = useState(0)const [other, setOther] = useState(0);const result = useMemo(() => ExpensiveCalculation(count), [count])return (<div><p>Result: {result}</p><button onClick={() => setCount(count + 1)}>Increment Count</button><button onClick={() => setOther(other + 1)}>Increment Other</button></div>);
}
export default App
- 第一个参数:回调函数
- 第二个参数(依赖数组):当依赖项发生变化时,useMemo会重新执行回调函数并返回新的计算结果,如果依赖项没有变化,useMemo会直接返回之前缓存的结果,而不是重新执行回调函数
注意事项
- useMemo只会缓存计算结果,不会缓存函数本身,如果需要缓存函数本身,使用useCallback
- 如果依赖项数组为空([]),useMemo的值只会在组件首次渲染时计算一次
useCallback
useCallback是React提供的一个Hook,用于换成函数的引用,避免在组件重新渲染时不必要的重新创建函数
基本用法
const memozedCallback = useCallback(() => {// 函数逻辑},[依赖项]
)
- 第一个参数:需要缓存的函数
- 第二个参数(依赖数组):当依赖项发生变化时,useCallback会返回一个新的函数引用;如果依赖项没有变化,则返回缓存的函数引用
为什么需要useCallback
- 在React中,函数组件每次渲染都会重新创建内部的函数,如果这些函数被传递给子组件或用作依赖项,可能会导致性能问题或不必要的副作用,
- 例如:子组件的不必要重新渲染;性能浪费,尤其在函数被频繁创建时
使用场景
- 避免子组件不必要的重新渲染
import React, { useState, useCallback, useEffect } from 'react';
// ({onClick}):表示从props中解构出onClick的属性
// ({onClick:() => void}):为onClick属性添加类型注解,表示他是一个函数,且没有参数,返回值为void
const Child = React.memo(({onClick}:{onClick:() => void})=>{console.log('子组件渲染')return <button onClick={onClick}>点击</button>
})function Parent(){const [count,setCount] = useState(0)const handleClick = useCallback(() => {console.log('按钮点击')}, [])return (<div><p>计数:{count}</p><button onClick={() => setCount(count+1)}>增加计数</button><Child onClick={handleClick} /></div>)
}
1:未使用useCallback时,每次Parent组件重新渲染时,handleClick都会被重新创建,导致Child组件也会重新渲染
2:使用useCallback后:handleClick的引用不会改变,Child组件不会重新渲染
- 作为依赖项传递给useEffect
import React, { useState, useCallback, useEffect } from 'react';
function Example(){const [count,setCount] = useState(0)const logCount = useCallback(() => {console.log(`当前计数:${count}`);}, [count])useEffect(() => {logCount()}, [logCount]) // 使用 useCallback 缓存的函数作为依赖项return <button onClick={() => setCount(count + 1)}>增加计数</button>;
}
1:未使用useCallback时:logCount 每次渲染都会重新创建,导致useEffect每次都重新执行
2:使用useCallback后:logCount只有在count 变化时才会更新,减少不必要的副作用
useCallback和React.memo的区别
特性 | useCallback | React.memo |
---|---|---|
作用对象 | 用于优化函数 | 用于优化组件 |
核心功能 | 避免函数在组件重新渲染时被重新创建 | 避免组件因props未变化而重新渲染 |
工作原理 | 缓存函数引用,只有依赖项变化时才重新创建 | 浅比较props,决定是否跳过组件渲染 |
适用场景 | 当函数被传递给子组件或用作依赖项时 | 当组件的props很少变化时 |
性能优化的目标 | 减少函数的重新创建次数 | 减少组件的重新渲染次数 |
useCallback和React.memo结合使用
在实际开发中,经常会被结合使用,尤其是在父组件向子组件传递函数时
useMemo和React.memo的区别
特性 | useMemo | React.memo |
---|---|---|
作用 | 缓存计算结果 | 优化组件渲染,避免不必要的重新渲染 |
触发条件 | 比较依赖项是否变化 | 比较组件的props是否变化 |
适用场景 | 用于函数或计算逻辑的性能优化 | 用于组件级别的性能优化 |
返回值 | 返回缓存的计算结果 | 返回优化后的组件 |
总结
- React.memo用于组件级别的性能优化,避免子组件不必要的重新渲染
- useMemo用于缓存计算结果,适合优化耗时的计算逻辑
- useCallback用于缓存函数引用,避免优化子组件接收函数props的场景