memo的问题
使用memo方法包裹组件,可以让组件不因为父组件的刷新而刷新,但是如果接收到了一个对象类型的props,这个memo就不能正常工作了。
import { useState, memo } from "react";
function App() {
const [count, setCount] = useState("");
const obj = () => {
return null;
};
return (
<>
<button onClick={() => setCount(count + 1)}>add inc</button>
<div>{count}</div>
<CompChildren />
<CompChildrenMemo name="wuzhao" />
<CompChildrenMemoPropsObj fn={obj} />
</>
);
}
function CompChildren() {
console.log("CompChildren render");
return <div>CompChildren</div>;
}
const CompChildrenMemo = memo(() => {
console.log("CompChildrenMemo render");
return <div>CompChildrenMemo</div>;
});
const CompChildrenMemoPropsObj = memo(() => {
console.log("CompChildrenMemoPropsObj render");
return <div>CompChildrenMemoPropsObj</div>;
});
export default App;
然而,React.memo 是用 shallowly compare 的方法确认 props 的值是否一样, shallowly compare 在 props 是 Number 或 String 比较的是值,当 props 是 Object 是,比較的是引用 (reference)。
因此,当父元件重新渲染时,在父元件宣告的 Object 都会被重新分配引用,所以想要利用 React.memo 防止重新渲染就会失效。
React.memo 提供了第二个参数
要解决这个问题的方法有两种,第一种是 React.memo 提供了第二个参数,让我们可以自订比较 props 的方法,让 Object 不再只是比较记忆体位置。
const CompChildrenMemoPropsObj = memo(
() => {
console.log("CompChildrenMemoPropsObj render");
return <div>CompChildrenMemoPropsObj</div>;
},
(preProps, nextProps) => {
if (_.isEqual(preProps, nextProps)) return false;
}
);
第二种方法则是 React.useCallback,让 React 可以自动记住 Object 的索引,解决 shallowly compare 的比较问题。
接下来,我们就来看看 React.useCallback。
React.useCallback 父组件上包裹
当父元件传递的 props 是 Object 时,父元件的状态被改变触发重新渲染,Object 的记忆体位址也会被重新分配。React.memo 会用 shallowly compare 比较 props 中 Object 的记忆体位址,这个比较方式会让子元件被重新渲染。
因此,React 提供了 React.useCallback 这个方法让 React 在元件重新渲染时,如果 dependencies array 中的值在没有被修改的情况下,它会帮我们记住 Object,防止 Object 被重新分配地址。
在父组件传递给子组件的方法上用useCallback包裹
const obj = useCallback(() => {
return null;
}, []);
useMemo
有一种情况是与父组件无关,是子组件内无关的状态变更导致整个组件的函数重新执行的问题,
下边是增加了inc这个没有用到的变量,但还是导致了factorial重新执行;
还有一种情况是这个子组件状态没有变更,但由于父组件的状态更新而刷新了,那这个组件里的useMemo包裹的方法也是不会执行的。
import { useState, useMemo } from "react";
export default function CalculateFactorial() {
const [number, setNumber] = useState(4);
const [inc, setInc] = useState(0);
// const factorial = factorialOf(number);
const factorial = useMemo(() => factorialOf(number), [number]);
const onChange = (event) => {
setNumber(Number(event.target.value));
};
const onClick = () => setInc((i) => i + 1);
return (
<div>
Factorial of
<input type="number" value={number} onChange={onChange} />
is {factorial}
<button onClick={onClick}>重新渲染</button>
</div>
);
}
function factorialOf(n) {
console.log("factorialOf(n) called!");
return n <= 0 ? 1 : n * factorialOf(n - 1);
}
summary
memo可以缓存组件防止被重复更新,但如果父组件传递了obj类型的props,则memo会失效,基本类型的props,memo还是可以正常工作。
解决memo接收obj类型的props的方式有两种,一种是memo的第二个参数,自定义比较器。一种是在父组件上把传递给子组件的引用使用useCallback包裹
useMemo与useCallback类似,不过useMemo缓存的是传入函数的执行结果。