避免在 Context 更新时运行效果挂钩

IT技术 reactjs react-hooks
2021-05-02 00:55:48

我有一个组件MyContainer,它有一个状态变量(通过useState钩子定义),定义了一个上下文提供者,它将状态变量作为值传递给它,并且还包含 2 个孩子,MySetCtxComponentMyViewCtxComponent.

MySetCtxComponent 可以更改存储在上下文中的值,调用 set 函数,该函数也作为上下文的一部分传递,但不渲染它。

MyViewCtxComponent,相反,RENDERS 存储在上下文中的值。

MySetCtxComponent还通过useEffect钩子定义了一个效果例如,此效果用于以固定时间间隔更新上下文的值。

所以3个组件的代码是这样的

我的容器

export function MyContainer() {
  const [myContextValue, setMyContextValue] = useState<string>(null);

  const setCtxVal = (newVal: string) => {
    setMyContextValue(newVal);
  };

  return (
    <MyContext.Provider
      value={{ value: myContextValue, setMyContextValue: setCtxVal }}
    >
      <MySetCtxComponent />
      <MyViewCtxComponent />
    </MyContext.Provider>
  );
}

MySetCtxComponent (加上一个全局变量使示例更简单)

let counter = 0;

export function MySetCtxComponent() {
  const myCtx = useContext(MyContext);

  useEffect(() => {
    console.log("=======>>>>>>>>>>>>  Use Effect run in MySetCtxComponent");
    const intervalID = setInterval(() => {
      myCtx.setMyContextValue("New Value " + counter);
      counter++;
    }, 3000);

    return () => clearInterval(intervalID);
  }, [myCtx]);

  return <button onClick={() => (counter = 0)}>Reset</button>;
}

MyViewCtxComponent

export function MyViewCtxComponent() {
  const myCtx = useContext(MyContext);

  return (
    <div>
      This is the value of the contex: {myCtx.value}
    </div>
  );
}

现在我的问题是,这样,每次更新上下文MySetCtxComponent时,即使MySetCtxComponent根本不需要,也会再次运行效果,因为在更新上下文时不需要渲染。但是,如果我myCtxuseEffect钩子的依赖项数组中删除(这会在上下文更新时阻止效果钩子),那么我会收到一个 es-lint 警告,例如React Hook useEffect has a missing dependency: 'myCtx'. Either include it or remove the dependency array react-hooks/exhaustive-deps.

最后的问题是:在这种情况下,忽略警告是安全的,还是我在这里有一个基本的设计错误,也许应该选择使用商店?考虑到该示例可能看起来很傻,但它是真实场景的最精简版本。

这是一个复制案例的堆栈闪电战

1个回答

解决此问题的一种模式是将上下文一分为二,为操作提供一个上下文,为访问上下文值提供另一个上下文。这使您可以正确实现 useEffect 的预期依赖项数组,同时也不会在仅上下文值发生更改时不必要地运行它。

const { useState, createContext, useContext, useEffect, useRef } = React;

const ViewContext = createContext();
const ActionsContext = createContext();

function MyContainer() {
  const [contextState, setContextState] = useState();

  return (
    <ViewContext.Provider value={contextState}>
      <ActionsContext.Provider value={setContextState}>
        <MySetCtxComponent />
        <MyViewCtxComponent />
      </ActionsContext.Provider>
    </ViewContext.Provider>
  )
}

function MySetCtxComponent() {
  const setContextState = useContext(ActionsContext);

  const counter = useRef(0);

  useEffect(() => {
    console.log("=======>>>>>>>>>>>>  Use Effect run in MySetCtxComponent");
    const intervalID = setInterval(() => {
      setContextState("New Value " + counter.current);
      counter.current++;
    }, 1000);

    return () => clearInterval(intervalID);
  }, [setContextState]);

  return <button onClick={() => (counter.current = 0)}>Reset</button>;
}

function MyViewCtxComponent() {
  const contextState = useContext(ViewContext);

  return (
    <div>
      This is the value of the context: {contextState}
    </div>
  );
}

ReactDOM.render(
  <MyContainer />,
  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>