React Context API 和 State Hooks 变化

3
我有一个使用上下文API和许多Hooks的现代React应用程序,我使用上下文来存储应用程序的全局值。这些值或上下文本身不应直接重新渲染其他组件。上下文本身具有自己的getter/setter形式,即UseState钩子函数,消费组件从中调用以使用它。如果任何组件依赖于上下文数据,则在此组件本身中创建单独的状态,然后正确处理该状态。
在我的情况下,我的具体问题是直接改变我存储在上下文中的对象有多糟糕?
例如,从任意随机消费者组件更改上下文对象如下:
const handlerFunction = () => {contextObjData.value = "Something"};

与其“预期”的react方式相反:

const handlerFunction = () => {setContextObjData(...contextObjData, value: "Something")};

对我来说,每次保存整个对象似乎有些过度,但也许有人可以给我另一种视角和一些见解。

顺便问一下,这两者之间有什么区别,我有点新手:

const handlerFunction = () => {setContextObjData(...contextObjData, value: "Something")};


const handlerFunction = () => {setContextObjData(prevState => ({...contextObjData, value: "Something"}));
1个回答

3

状态的改变会触发重新渲染。当你改变某个状态时,React 不会检测到状态已经改变并且不会重新渲染。

如果想要通过使用 useCallback 进行优化,则 handlerFunction 的示例非常重要,但是无论如何你都做破坏它的方式(第二个示例中存在语法错误并且没有使用 prevState )。

//handlerFunction will be re created every render
const handlerFunction = () =>
  setContextObjData({
    ...contextObjData,
    value: 'Something',
  });
//handler function will only be created on mount
const optimizedHandler = React.useCallback(
  () =>
    setContextObjData((prevState) => ({
      ...prevState,
      value: 'Something',
    })),
  [] //empty dependency, only create optimizedHandler on mount
);
//broken handler, using stale closure will give you liner warning
const brokenHandler = React.useCallback(
  () =>
    //callback only used on mount so has contextObjData
    //  as it was when mounted (stale closure)
    setContextObjData({
      ...contextObjData, //needs contextObjData in closure scope
      value: 'Something',
    }),
  [] //empty dependency, but contextObjData will be a stale closure
);

纯组件只会在 props,状态或 useSelector 或 useContext 返回值更改时重新渲染。

当您将回调作为 prop 传递给子级,并且您的组件重新渲染而无需子级重新渲染时,您可以使用 useCallback 优化传递的回调,以便子级不会被不必要地重新渲染:

//Child is a pure component
const Child = React.memo(function Increment({ increment }) {
  const r = React.useRef(0);
  r.current++;
  return (
    <button onClick={increment}>
      rendered: {r.current} times, click to increment
    </button>
  );
});
const Parent = () => {
  const [count, setCount] = React.useState(1);
  const increment = React.useCallback(
    () => setCount((c) => c + 1),
    []
  );
  React.useEffect(() => {
    const t = setInterval(() => increment(), 1000);
    return () => clearInterval(t);
  }, [increment]);
  return (
    <div>
      <h4>{count}</h4>
      <Child increment={increment} />
    </div>
  );
};
ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

这里是一个示例,说明如何通过突变来破坏重新渲染:

const CounterContext = React.createContext();
const CounterProvider = ({ children }) => {
  console.log('render counter provider');
  const [c, setC] = React.useState({ count: 0 });
  const increment = React.useCallback(
    () =>
      setC((c) => {
        console.log('broken:', c.count);
        c.count++;
        return c;
      }), //broken, context users never re render
    []
  );
  return (
    <CounterContext.Provider value={[c, increment]}>
      {children}
    </CounterContext.Provider>
  );
};
const App = () => {
  console.log('render App');
  const [count, increment] = React.useContext(
    CounterContext
  );
  return (
    <div>
      <h4>count: {count.count}</h4>
      <button onClick={increment}>+</button>
    </div>
  );
};
ReactDOM.render(
  <CounterProvider>
    <App />
  </CounterProvider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>


@Steytz,我添加了一个示例,展示了突变上下文不会导致重新渲染并破坏您的应用程序。不确定您所说的“您不会更改但需要突变”的含义是什么,突变是否会更改数据?您问题中的示例会更改数据,但不会导致任何重新渲染,因此不会更新使用该数据的任何组件。 - HMR
1
@Steytz 如果你从未在组件中使用上下文,那么拥有上下文就毫无意义,但你可以轻松地改变它。 - HMR
我使用它来全局存储数据,以便所有组件都可以访问此全局数据。其中大部分内容仅供组件消耗而非更改,因此不会重新渲染任何内容,例如哪些问题已在测验中回答或实际问题是什么等数据。基本上,我使用它来在所有组件之间存储数据的位置,然后将其持久化到数据库中,但正如您所看到的,这是次要的信息数据,不直接影响任何组件的状态周期。 - Steytz
对于这些数据可能影响渲染的组件,我仍然为该组件拥有一个单独的状态,其中将contextData设置为默认值,然后像通常一样对正常状态进行更改。 - Steytz
1
谢谢回复! - Steytz
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接