什么是useMemo
?
useMemo
函数可以缓存已有的计算结果,在组件发生更新进行重新渲染时,会采用已有的缓存结果,而不是重新进行计算,从而达到性能优化的效果。
如果你使用过Vue
,那么useMemo
就和Vue
中的computed
类似。
//useMemo有两个参数
//第一个参数factory:用来处理业务逻辑的计算函数,通常是计算比较占用资源的函数
//第二个参数deps:依赖列表,当依赖列表中的依赖项发生改变时,会重新调用factory函数进行计算
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
useMemo
的用法?
//computeExpensiveValue值计算比较占用性能的函数
//a,b为依赖项,当a,b的值发生改变时,在重新渲染的时候会重新计算computeExpensiveValue
//这种优化有助于避免在每次渲染时都进行高开销的计算。
const [a,setA]=useState(0);
ocnst [b,setb]=useState(100000000000);
const memoizedValue = useMemo(() => computeExpensiveValue, [a, b]);
//a,b改变时重新计算
cosnt computeExpensiveValue=()=>{
let sum=0;
for(let i=a,i<b;i++)
{
sum=sum+i;
}
return sum;
//return <div>{sum}<div>//或者返回渲染元素
//
}
//调用 memoizedValue
记住,传入 useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如清除副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
。
如果没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值。
//computeExpensiveValue函数在组件每次渲染事都会调用
//没必要useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b));
//computeExpensiveValue函数只计算一次
const memoizedValue = useMemo(() => computeExpensiveValue(a, b),[]);
**你可以把 useMemo
作为性能优化的手段,但不要把它当成语义上的保证。**将来,React 可能会选择“遗忘”以前的一些 已经缓存的 值,(即使你使用了useMemo
)并在下次渲染时重新计算它们,比如为离屏组件(一些不在可视范围内没有必要渲染的组件)释放内存。
优化代码性能的方法:先编写在没有 useMemo
的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo
,以达到优化性能的目的。
useMemo
和React.memo
的优化方式,都是通过缓存的方式进行优化,React.memo
是高阶u组件爱你,通过React.memo()
包裹组件,可以让组件只在props
的值发生改变(默认进行浅比较,第二个参数可以指定比较方式)时,才会重新渲染组件。
什么是useCallback
?
useCallback
和useMemo
比较相似,只不过useMemo
缓存的是值,useCallback
缓存的是函数。把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized
版本,该回调函数仅在某个依赖项改变时才会更新,否则在组件重新渲染的时候,该函数也不会重新生成,而是采用已经缓存的版本,这点很重要。
//callback:内联回调函数
//deps:重新生成函数对象的依赖项
//返回值:生产一个新的函数,功能和内联回调函数的功能一样
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
useCallback
用法
下面有一个简单的例子
function APP() {
const [value, setValue] = useState(123)
const [otherValue, setOtherValue] = useState(123)
const changeValue = useCallback(() => {
setValue(value => value+1)
}, [value])
// 点击父组件的按钮,父组件的数据发生更新,子组件不会重新渲染
return (
<div>
<div>测试数据==={otherValue}</div>
<button onClick={() => setOtherValue(value => value-=5)}>改变无关的数据,引起组件更新</button>
<Message value={value} changeValue={changeValue} />
</div>
)
}
const Message = memo(
function Message({value, changeValue}) {
console.log('渲染了');
//点击子组件的按钮,子组件的值value发生改变了,子组件会重新渲染
return (
<div>
<button onClick={changeValue}>改变有关数据</button>
<p>与Message渲染有关的数据{value}</p>
</div>
)
}
)
代码解释:
我们知道react
的hook
组件在state
和props
发生改变时,会重新渲染组件,从而导致子组件也会被重新渲染,但有些时候,子组件的的props
和state
值没有变化,子组件就没必要重新渲染(万一子组件渲染比较耗时呢)这样就可以进行优化。所有使用React.memo
函数包裹子组件,这样在传递给子组件的props
的值没有改变时,子组件就不会重新被渲染,就是Message
组件 props
中value
属性的值没有发生改变,Message
组件就应该不会重新渲染。
但是Message
组建的props
中还有一个函数属性changeValue
,在副组件重新渲染时候,会重新生成一个changValue
函数对象,导致传入Message
组件的changeValue
对象发生改变,从而引起Message
组件重新渲染。我们是不希望父组件中changeValue
函数重新生成的,第一它在重新渲染时没有什么改变,第二它重新生成还会导致子组件被重新渲染。那么我们可以把这个函数的功能缓存下来,每次使用缓存的函数,就不会导致重新渲染了,useCallback
就是做这个的。
//会缓存函数状态,函数不会改变 ,函数中value的值永远也不会改变,是初始值缓存的状态
const changeValue = useCallback(() => {
setValue(value => value+1)
}, [])
//会缓存函数状态,在value改变时,改变函数状态 ,函数中value的值也会改变
//注意,如果useCallback中函数使用到了父组件state中的变量,一定要放在依赖数组中,如value
//否则 缓存的函数的value值永远为初始状态的值
const changeValue = useCallback(() => {
setValue(value => value+1)
}, [value])
//不会缓存函数,每次组件重新渲染时都会重新生成一个新的changeValue函数对象,引起props发生变化
const changeValue = () => {
setValue(value => value+1)
}
一般useCallback
函数会配合React.memo()
函数一起使用的,利用缓存函使子组件不会重新渲染,如果不使用React.memo()
函数,子组件是会重新渲染的。
React.memo
函数
React.memo
为高阶组件。
如果你的组件在相同 props
的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo
中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
//第一个参数为要包裹的组件
//第二个参数为前后props值的比较方式判断props是否改变,默认是浅比较
function memo<T extends ComponentType<any>>(
Component: T,
propsAreEqual?: (prevProps: Readonly<ComponentProps<T>>, nextProps: Readonly<ComponentProps<T>>) => boolean
): MemoExoticComponent<T>;
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
默认使用
//浅比较
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
自定义比较函数
const Message = memo(
function Message({value, changeValue}) {
console.log('渲染了');
//点击子组件的按钮,子组件的值value发生改变了,子组件会重新渲染
return (
<div>
<button onClick={changeValue}>改变有关数据</button>
<p>与Message渲染有关的数据{value}</p>
</div>
)
},
(prve,next)=>{
//只要value值相等,就认为props值没有发生改变
//这样就不会渲染,和useCallback实现的不重新渲染效果一样,但是原理不一样
//不重新渲染,导致changeValue不会发生改变
//value的值永远为执行了一次changeValue的值
//不要这么用,还是useCallback(()=>{},[value])这样
if(prve.value==next.value){
return true;
}
}
)
React.memo
仅检查 props 变更。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
,useReducer
或 useContext
的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。