【问题标题】:Only conditionally re-render when context value updates仅在上下文值更新时有条件地重新渲染
【发布时间】:2021-10-11 12:53:42
【问题描述】:

我正在开发一个大型 React 应用程序,其中性能至关重要,不必要的重新渲染代价高昂。

我有以下例子:

const CounterContext = React.createContext();

const CounterProvider = ({children}) => {
    const [counterA, setCounterA] = React.useState(0);
    const [counterB, setCounterB] = React.useState(0);
  
  return (
    <CounterContext.Provider value={{counterA, counterB}}>
      {children}
      <button onClick={() => setCounterA(counterA + 1)}>Counter A ++</button>
      <button onClick={() => setCounterB(counterB + 1)}>Counter B ++</button>
      <button onClick={() => {setCounterA(0); setCounterB(0)}}>reset</button>
    </CounterContext.Provider>
  )
}

const CounterA = () => {
  const value = React.useContext(CounterContext);
  console.log('CounterA re-render');
  return <p>Counter A: {value.counterA}</p>;
}

const CounterB = () => {
  const value = React.useContext(CounterContext);
  console.log('CounterB re-render');
  return <p>Counter B: {value.counterB}</p>;
};

const App = () => {
    return (
    <CounterProvider>
      <CounterA />
      <CounterB />
    </CounterProvider>
  )
};

jsfiddle:https://jsfiddle.net/mitchkman/k3hm0vfq/1/

单击 CounterA 按钮时,两个 CounterACounterB 组件将重新渲染。如果value 中的counterA 属性发生变化,我只想重新渲染CounterA

我还希望能够为条件重新渲染提供某种形式的灵活性。这是我正在尝试做的伪代码

const MyComponent = () => {

  // Only re-render MyComponent if value.property equals 42
  const value = useContext(MyContext, (value) => value.property === 42);
  ...

};

【问题讨论】:

  • 您想尝试阻止CoutnerA 的任何重新呈现,还是在满足谓词(即counterA === 42)之前不暴露更新的counterA 状态值? “如果值中的 counterA 属性发生变化,我只想重新渲染 CounterA。”这就是开箱即用的工作方式。如果您不希望 CounterA 由于其他原因重新渲染,请使用 memo 高阶组件并传递自定义 areEqual 函数。

标签: javascript reactjs publish-subscribe react-context


【解决方案1】:

您可能必须将 counterProvider 包装到您的 App 组件中。并使用React.memo 实现仅在该组件的值更改时重新渲染。

我在sandbox这里做过类似的事情

index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { CounterProvider } from "./AppContext";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <CounterProvider>
      <App />
    </CounterProvider>
  </StrictMode>,
  rootElement
);

counterContext 移动到一个单独的文件中,以便您可以导入和包装您的App 组件。

AppContext.js

import React, { useCallback } from "react";

const CounterContext = React.createContext();

export const CounterProvider = ({ children }) => {
  const [counterA, setCounterA] = React.useState(0);
  const [counterB, setCounterB] = React.useState(0);

  const incrementA = () => {
    setCounterA(counterA + 1);
  };
  const incrementB = () => {
    setCounterB(counterB + 1);
  };

  return (
    <CounterContext.Provider value={{ counterA, counterB }}>
      {children}
      <button onClick={incrementA}>Counter A ++</button>
      <button onClick={incrementB}>Counter B ++</button>
      <button
        onClick={useCallback(() => {
          setCounterA(0);
          setCounterB(0);
        }, [])}
      >
        reset
      </button>
    </CounterContext.Provider>
  );
};

export default CounterContext;

将 counterA 和 counterB 值作为 props 传递给 Counter 组件,以便React.memo 可以在重新渲染组件之前检查该值。

App.js

import React from "react";
import CounterContext from "./AppContext";

const CounterA = React.memo(({ value }) => {
  console.log("CounterA re-render");
  return <p>Counter A: {value}</p>;
});

const CounterB = React.memo(({ value }) => {
  console.log("CounterB re-render");
  return <p>Counter B: {value}</p>;
});

const App = () => {
  const value = React.useContext(CounterContext);
  const counterA = value.counterA;
  const counterB = value.counterB;

  console.log(counterA);

  return (
    <>
      <CounterA value={counterA} />
      <CounterB value={counterB} />
    </>
  );
};

export default App;

【讨论】:

    猜你喜欢
    • 2016-12-30
    • 1970-01-01
    • 2021-01-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 2022-01-06
    • 2021-09-02
    • 2021-06-07
    相关资源
    最近更新 更多