现象
React 中,通常父组件的某个state发生改变,会引起父组件的重新渲染(和其他state的重新计算),从而会导致子组件的重新渲染(和其他非相关属性的重新计算)
- 问题一: 如何避免因为某个state变化,导致组件的中其他属性(state)的重新计算?
**方案:**useMemo
- 问题二: 如何避免因为父组件的重新渲染,导致子组件中非相关属性的重新计算?
**方案:**React.memo
结论
总结起来就一句话:
React.memo 用来限制组件重新渲染,而 useMemo用来限制组件中的部分变量重新计算
(前者主要针对组件的渲染,后者则侧重于组件内的计算)
示例
React.memo :【常用于子组件】
它是一个高阶组件(Higher-Order Component,HOC),用于包装函数组件。
当父组件重新渲染时,往往会触发子组件的重新渲染。但很多时候子组件的 props
并没有改变,此时子组件的重新渲染就是不必要的,会造成性能浪费。
经过 React.memo
包装的组件,React 会对其 props
进行浅比较,如果新 props
和旧 props
相同,组件不会重新渲染,而是复用之前的渲染结果。(浅比较只会检查对象或数组的引用是否相同,而不会深入比较其内部的属性或元素)
javascript
import React from 'react';
// 使用 React.memo 包裹组件
const MyComponent = React.memo(({ data }) => {
console.log('组件重新渲染');
return <div>{data}</div>;
});
const ParentComponent = () => {
const [count, setCount] = React.useState(0);
const someData = '固定数据';
return (
<div>
{/* 如果子组件不用React.memo包裹,count变化后子组件也会重新渲染 */}
<button onClick={() => setCount(count + 1)}>增加计数</button>
{/* 只要 someData 不变,MyComponent 不会重新渲染 */}
<MyComponent data={someData} />
</div>
);
};
export default ParentComponent;
useMemo:
这是一个 React Hook,只能用于函数组件内部。
它主要用于缓存计算结果,根据传入的依赖项数组来判断是否需要重新计算缓存的值,避免在每次组件渲染时都进行重复的高开销计算。
如果计算属性作为子组件的 props
传递,且子组件使用 React.memo
进行了优化,在父组件使用 useMemo
可以确保计算属性的引用在依赖项不变时保持稳定,从而避免子组件不必要的重新渲染。
javascript
import React, { useState, useMemo, memo } from 'react';
const ChildComponent = memo(({ data }) => {
console.log('ChildComponent rendered');
return <div>{data}</div>;
});
const ParentComponent = () => {
const [num, setNum] = useState(1);
const calculatedData = useMemo(() => {
return num * 2;
}, [num]);
return (
<div>
<input
type="number"
value={num}
onChange={(e) => setNum(Number(e.target.value))}
/>
<ChildComponent data={calculatedData} />
</div>
);
};
export default ParentComponent;
这里 calculatedData
通过 useMemo
缓存,当 num
不变时,calculatedData
的引用保持不变,ChildComponent
不会因为 props
的引用变化而重新渲染。
顺便提一下【useCallback】:
当父组件向子组件传递一个函数作为 props
,并且子组件使用 React.memo
包裹时,useCallback
可以确保该函数的引用在依赖项不变时保持稳定,从而避免子组件因为函数引用的改变而进行不必要的重新渲染。
javascript
import React, { useState, useCallback, memo } from 'react';
// 使用 React.memo 包裹子组件
const ChildComponent = memo(({ onClick }) => {
console.log('ChildComponent 渲染');
return <button onClick={onClick}>点击我</button>;
});
const ParentComponent = () => {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>计数: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
};
export default ParentComponent;