【React】使用useMemo来改善ContextAPI+useState的渲染性能
首先
请注意,我只能提供英语翻译。
在React的ContextAPI中,如果不小心滥用useState,会导致渲染成本增加,从而导致性能下降的可能性。因此,本文介绍了一种通过使用useMemo来选择性地编写重新渲染组件以提高性能的方法。
组件结构
(为了方便理解,在这里我们根据需要为组件应用了相应的CSS)
没有对策的版本
代码示例
import { createContext } from "react";
// Contextの型を定義
type ValueContextType = {
value1: number;
updateValue1: (_: number) => void;
value2: number;
updateValue2: (_: number) => void;
value3: number;
updateValue3: (_: number) => void;
};
// Contextを作成
const ValueContext = createContext<ValueContextType>(null!);
// Providerを作成
const ValueProvider = ({ children }: { children: JSX.Element }) => {
const [value1, setValue1] = useState(0);
const updateValue1 = (value: number) => {
setValue1(value);
};
const [value2, setValue2] = useState(0);
const updateValue2 = (value: number) => {
setValue2(value);
};
const [value3, setValue3] = useState(0);
const updateValue3 = (value: number) => {
setValue3(value);
};
return (
<ValueContext.Provider value={{
value1,
updateValue1;
value2,
updateValue2;
value3,
updateValue3;
}}>
{children}
</ValueContext.Provider>
);
};
// ContextとProviderをエクスポート
export { ValueContext, ValueProvider };
import { useContext } from "react";
import { ValueContext } from "./Context.tsx";
import Child1 from "./Child1";
import Child2 from "./Child2";
// Parentコンポーネント
const Parent = () => {
console.log("re-rendered Parent");
const { value1, updateValue1 } = useContext(ValueContext);
return (
<div>
<h2>Parent</h2>
<p>{value1}</p>
<button type="button" onClick={() => {
updateValue1(value1 + 1);
}}>+</button>
<Child1 />
<Child2 />
</div>
);
};
export default Parent;
import { useContext } from "react";
import { ValueContext } from "./Context.tsx";
// Child1コンポーネント
const Child1 = () => {
console.log("re-rendered Child1");
const { value2, updateValue2 } = useContext(ValueContext);
return (
<div>
<h2>Child1</h2>
<p>{value2}</p>
<button type="button" onClick={() => {
updateValue2(value2 + 1);
}}>+</button>
</div>
);
};
export default Child1;
import { useContext } from "react";
import { ValueContext } from "./Context.tsx";
// Child2コンポーネント
const Child2 = () => {
console.log("re-rendered Child2");
const { value3, updateValue3 } = useContext(ValueContext);
return (
<div>
<h2>Child2</h2>
<p>{value3}</p>
<button type="button" onClick={() => {
updateValue3(value3 + 1);
}}>+</button>
</div>
);
};
export default Child2;
export default function MyApp() {
return (
<ValueProvider>
<Parent />
</ValueProvider>
);
}
问题点
当value1被更新时,Child1、Child2组件也会重新渲染。
在React中,当父组件重新渲染时,子组件也会重新渲染,这是React的原则。尽管这不一定是问题,但随着应用规模的增大,可能会导致性能下降的可能性。
【文献参考】
当value2和value3更新时,Parent组件也会重新渲染。
当value2、value3的值更新时,只希望重新渲染发生更改的子组件,但不希望发生不必要的重新渲染,包括父组件和兄弟组件的重新渲染。我认为这个问题更加重要。
在这种情况下重新渲染的原因是Context的值发生变化时,由于使用了ContextAPI机制,调用了useContext的所有组件都会重新渲染。谢谢@honey32的指正。
【引用于公开文件】
当Provider的value属性更改时,所有作为Provider后代的消费者将重新渲染。从Provider到其后代消费者的传播(包括.contextType和useContext)不受shouldComponentUpdate方法限制,因此即使祖先组件跳过更新,消费者也会更新。
在所有是Provider的子孙的Consumer中,每当Provider的value prop发生变化时,它们都会重新渲染。从Provider传播到其子孙Consumer的更新不受shouldComponentUpdate方法的影响,因此即使祖先组件跳过了更新,Consumer也会被更新。
在这里所说的Provider的价值主张是
<ValueContext.Provider value={{
value1,
updateValue1;
value2,
updateValue2;
value3,
updateValue3;
}}>
这指的是这一部分。
换言之,一旦上下文的属性值被更新
const { (略) } = useContext(ValueContext);
所有包含这段代码的组件都会被更新。考虑到React的设计思想,这是一个自然而然的结果,但这可能会导致性能下降。
【参考报道】
改善计划
通过使用React的标准API useMemo,可以将组件返回的DOM(确切地说是React元素)进行记忆化,以减少不必要的渲染。
有解决方案的版本
代码示例
import { useContext, useMemo } from "react";
import { ValueContext } from "./Context.tsx";
import Child1 from "./Child1";
import Child2 from "./Child2";
// Parentコンポーネント
const Parent = () => {
const { value1, updateValue1 } = useContext(ValueContext);
return useMemo(() => { // React要素をメモ化
console.log("re-rendered Parent");
return (
<div>
<h2>Parent</h2>
<p>{value1}</p>
<button type="button" onClick={() => {
updateValue1(value1 + 1);
}}>+</button>
<Child1 />
<Child2 />
</div>
);
}, [value1]); // value1が更新されたときのみ再レンダリング
};
export default Parent;
import { useContext, useMemo } from "react";
import { ValueContext } from "./Context.tsx";
// Child1コンポーネント
const Child1 = () => {
const { value2, updateValue2 } = useContext(ValueContext);
return useMemo(() => { // React要素をメモ化
console.log("re-rendered Child1");
return (
<div>
<h2>Child1</h2>
<p>value2: {value2}</p>
<button type="button" onClick={() => {
updateValue2(value2 + 1);
}}>+</button>
</div>
);
}, [value2]); // value2が更新されたときのみ再レンダリング
};
export default Child1;
import { useContext, useMemo } from "react";
import { ValueContext } from "./Context.tsx";
// Child2コンポーネント
const Child2 = () => {
const { value3, updateValue3 } = useContext(ValueContext);
return useMemo(() => { // React要素をメモ化
console.log("re-rendered Child2");
return (
<div>
<h2>Child2</h2>
<p>value3: {value3}</p>
<button type="button" onClick={() => {
updateValue3(value3 + 1);
}}>+</button>
</div>
);
}, [value3]); // value3が更新されたときのみ再レンダリング
};
export default Child2;
执行结果
仅重新渲染了发生变化的组件。准确地说,组件函数本身会执行,但只要useMemo的第2个参数值不变,就不会发生重新渲染的计算,因此可以提高性能。
最后
我对React还是一个初学者,所以如果有任何错误或需要修改的地方,请务必在评论中指出,谢谢您的帮助。
相关文章