仅在上下文值更新时有条件地重新渲染

Posted

技术标签:

【中文标题】仅在上下文值更新时有条件地重新渲染【英文标题】: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 函数。 【参考方案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;

【讨论】:

以上是关于仅在上下文值更新时有条件地重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

仅在第一个组件通过验证时有条件地更新第二个组件

React Hooks 有条件地渲染登录/注销 w/本地存储变量

西装外套 |炽热 | RadioGroup \ Radio:更新数据上下文后重新渲染页面元素

在使用数据库数据更新上下文后,React 'useContext' 钩子不会重新渲染

盖茨比:上下文更新导致无限渲染循环

从消费者 componentDidMount 更新反应上下文会导致无限重新渲染