【React】使用useMemo来改善ContextAPI+useState的渲染性能

首先

请注意,我只能提供英语翻译。

在React的ContextAPI中,如果不小心滥用useState,会导致渲染成本增加,从而导致性能下降的可能性。因此,本文介绍了一种通过使用useMemo来选择性地编写重新渲染组件以提高性能的方法。

组件结构

image.png

(为了方便理解,在这里我们根据需要为组件应用了相应的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还是一个初学者,所以如果有任何错误或需要修改的地方,请务必在评论中指出,谢谢您的帮助。

相关文章

 

广告
将在 10 秒后关闭
bannerAds