避免在 Context 更新时运行效果挂钩 [英] Avoid runnning an effect hook when Context get updated

查看:34
本文介绍了避免在 Context 更新时运行效果挂钩的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

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

我的容器

导出函数 MyContainer() {const [myContextValue, setMyContextValue] = useState(null);const setCtxVal = (newVal: string) =>{setMyContextValue(newVal);};返回 (<MyContext.Providervalue={{ value: myContextValue, setMyContextValue: setCtxVal }}><MySetCtxComponent/><MyViewCtxComponent/></MyContext.Provider>);}

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

let counter = 0;导出函数 MySetCtxComponent() {const myCtx = useContext(MyContext);useEffect(() => {console.log("========>>>>>>>>>>> 在 MySetCtxComponent 中使用效果运行");const intervalID = setInterval(() => {myCtx.setMyContextValue("新值" + counter);计数器++;}, 3000);返回 () =>清除间隔(间隔ID);}, [myCtx]);return 

MyViewCtxComponent

导出函数 MyViewCtxComponent() {const myCtx = useContext(MyContext);返回 (<div>这是上下文的值:{myCtx.value}

);}

现在我的问题是,这样,每次更新上下文时,MySetCtxComponent 的效果都会再次运行,即使这根本不需要,因为 MySetCtxComponent 确实如此不需要在上下文更新时渲染.但是,如果我从 useEffect 钩子的依赖数组中删除 myCtx(这会在上下文更新时阻止效果钩子),那么我会收到一个 es-lint 警告,例如因为 React Hook useEffect 缺少依赖项:'myCtx'.要么包含它,要么删除依赖数组 react-hooks/exhaustive-deps.

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

这里是一个stackblitz复制案例

解决方案

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

const { useState, createContext, useContext, useEffect, useRef } = React;const ViewContext = createContext();const ActionsContext = createContext();函数 MyContainer() {const [contextState, setContextState] = useState();返回 (<ViewContext.Provider value={contextState}><ActionsContext.Provider value={setContextState}><MySetCtxComponent/><MyViewCtxComponent/></ActionsContext.Provider></ViewContext.Provider>)}函数 MySetCtxComponent() {const setContextState = useContext(ActionsContext);const 计数器 = useRef(0);useEffect(() => {console.log("========>>>>>>>>>>>> 在 MySetCtxComponent 中使用效果运行");const intervalID = setInterval(() => {setContextState("新值" + counter.current);counter.current++;}, 1000);返回 () =>清除间隔(间隔ID);}, [setContextState]);return 

);}ReactDOM.render(<我的容器/>,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>

I have a component MyContainer which has a state variable (defined via useState hook), defines a context provider to which it passes the state variable as value and contains also 2 children, MySetCtxComponent and MyViewCtxComponent.

MySetCtxComponent can change the value stored in the context invoking a set function which is also passed as part of the context, BUT DOES NOT RENDER it.

MyViewCtxComponent, on the contrary, RENDERS the value stored in the context.

MySetCtxComponent defines also an effect via useEffect hook. This effect is, for instance, used to update the value of the context at a fixed interval of time.

So the code of the 3 components is this

MyContainer

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 (plus a global varibale to make the example simpler)

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>
  );
}

Now my problem is that, in this way, everytime the context is updated the effect of MySetCtxComponent is run again even if this is not at all required since MySetCtxComponent does not need to render when the context is updated. But, if I remove myCtx from the dependency array of the useEffect hook (which prevents the effect hook when the context get updated), then I get an es-lint warning such as React Hook useEffect has a missing dependency: 'myCtx'. Either include it or remove the dependency array react-hooks/exhaustive-deps.

Finally the question: is this a case where it is safe to ignore the warning or do I have a fundamental design error here and maybe should opt to use a store? Consider that the example may look pretty silly, but it is the most stripped down version of a real scenario.

Here a stackblitz to replicate the case

解决方案

One pattern for solving this is to split the context in two, providing one context for actions and another for accessing the context value. This allows you to fulfill the expected dependency array of the useEffect correctly, while also not running it unnecessarily when only the context value has changed.

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>

这篇关于避免在 Context 更新时运行效果挂钩的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆