上篇文章,有一个问题,每一次点击 add ,2个子组件的打印都会打印,但是 useRef 的子组件的 props 并没有更新, 理论上来说是不需要更新的。那么应该如何优化呢,使得点击 add 后只有 countRef.current 的值变了,countRef.current 的子组件才更新。
答案就是使用 memo
import React, { useState, useRef, memo } from "react";
const hasOwnProperty = Object.prototype.hasOwnProperty;
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
// react 源码中的比较函数
function shallowEqual(objA: any, objB: any): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
const Children = ({ name, value }: { name: string; value: number }) => {
// 每一次子组件更新时,都会触发
console.log(name, ":", value, "render");
return (
<div>
{name}-Children:{value}
</div>
);
};
// 每一次更新都比较前后的 props 是否相同,如果返回 true 就不渲染,返回 false 才渲染。
const MemoChildren = memo(Children, (prevProps, currProps) => {
console.log(
prevProps,
currProps,
"equal:",
shallowEqual(prevProps, currProps)
);
return shallowEqual(prevProps, currProps);
});
const CounterWithRef = () => {
// 使用 useState 来管理计数状态
const [count, setCount] = useState(0);
// 使用 useRef 来存储计数的持久性引用,不会导致重新渲染
const countRef = useRef(0);
// 每一次页面更新时,都会触发
console.log("count:", count, "countRef.current:", countRef.current, "render");
const handleAdd = () => {
// 使用 useState 更新状态,并触发重新渲染
setCount((prevCount) => prevCount + 1);
};
const handleRefAdd = () => {
// 使用 useRef 直接更新引用,不触发重新渲染
countRef.current = countRef.current + 1;
};
const handleCheck = () => {
// 用于实时查看 useState 和 useRef 的值
console.log(
"count:",
count,
"countRef.current:",
countRef.current,
"check"
);
};
return (
<div>
<div style={{ display: "flex", gap: "6px" }}>
<p>Count (useState): {count}</p>
<button type="button" onClick={handleAdd}>
add
</button>
</div>
<div style={{ display: "flex", gap: "6px" }}>
<p>Count (useRef): {countRef.current}</p>
<button onClick={handleRefAdd}>refAdd</button>
</div>
<div onClick={handleCheck}>check</div>
<MemoChildren name="useState" value={count}></MemoChildren>
<MemoChildren name="useRef" value={countRef.current}></MemoChildren>
</div>
);
};
export default CounterWithRef;
子组件现在都被 memo 包装了一层,现在点击 add,可以看见比较的函数每一次都会触发,只有当 equal 为 false 时,组件的打印才会触发,也就代表了只有 props 更新了,子组件才更新。