如何正确使用 useEffect 与 useContext 作为依赖

Posted

技术标签:

【中文标题】如何正确使用 useEffect 与 useContext 作为依赖【英文标题】:How to use useEffect correctly with useContext as a dependency 【发布时间】:2021-11-16 06:11:16 【问题描述】:

我正在处理我的第一个 React 项目,但遇到以下问题。 我希望我的代码如何工作:

我将项目添加到可通过上下文访问的数组 (context.items) 我想在组件中运行 useEffect 函数,每当值发生变化时都会显示 context.items

我尝试了什么:

    将上下文(contextcontext.items)列为 useEffect 中的依赖项
这导致组件在值更改时不会更新
    列出context.items.length
这会导致组件在数组长度发生变化时更新,而不是在单个项目的值发生变化时更新。
    将上下文包装在Object.values(context)
结果正是我想要的,除了 React 现在抱怨 *传递给 useEffect 的最后一个参数改变了渲染之间的大小。此数组的顺序和大小必须保持不变。 *

你知道有什么方法可以修复这个 React 警告,或者在上下文值更改时运行 useEffect 的不同方法吗?

好吧,我不想添加代码,希望这会是我这边的一些简单错误,但即使有一些答案,我仍然无法解决这个问题,所以在这里,为了简化而减少。

上下文组件:

const NewOrder = createContext(
  orderItems: [
    itemId: "",
    name: "",
    amount: 0,
    more:[""]
  ],
  addOrderItem: (newOItem: OrderItem) => ,
  removeOrderItem: (oItemId: string) => ,
  removeAllOrderItems: () => ,
);

export const NewOrderProvider: React.FC = (props) => 
  // state
  const [orderList, setOrderList] = useState<OrderItem[]>([]);

  const context = 
    orderItems: orderList,
    addOrderItem: addOItemHandler,
    removeOrderItem: removeOItemHandler,
    removeAllOrderItems: removeAllOItemsHandler,
  ;

  // handlers
  function addOItemHandler(newOItem: OrderItem) 
    setOrderList((prevOrderList: OrderItem[]) => 
      prevOrderList.unshift(newOItem);
      return prevOrderList;
    );
  
  function removeOItemHandler(oItemId: string) 
    setOrderList((prevOrderList: OrderItem[]) => 
      const itemToDeleteIndex = prevOrderList.findIndex((item: OrderItem) => item.itemId === oItemId);
      console.log(itemToDeleteIndex);
      prevOrderList.splice(itemToDeleteIndex, 1);
      return prevOrderList;
    );
  
  function removeAllOItemsHandler() 
    setOrderList([]);
  

  return <NewOrder.Provider value=context>props.children</NewOrder.Provider>;
;

export default NewOrder;

显示数据的组件(实际上是一个模态):

const OrderMenu: React.FC< isOpen: boolean; hideModal: Function > = (
  props
) => 
const NewOrderContext = useContext(NewOrder);
useEffect(() => 
    if (NewOrderContext.orderItems.length > 0) 
      const oItems: JSX.Element[] = [];
      NewOrderContext.orderItems.forEach((item) => 
        const fullItem = 
          itemId:item.itemId,
          name: item.name,
          amount: item.amount,
          more: item.more,
        ;
        oItems.push(
          <OItem item=fullItem editItem=() => editItem(item.itemId) key=item.itemId />
        );
      );
      setContent(<div>oItems</div>);
     else 
      exit();
    
  , [NewOrderContext.orderItems.length, props.isOpen]);

代码中的一些 cmets:

它实际上是在 Type Script 中完成的,这涉及到一些额外的语法 -content(和设置内容)是一种状态,它是返回值的一部分,因此可以动态设置某些部分 -exit 是一个关闭模态的函数,也是为什么 props.is Open 被包含进来的原因 有了这个.length 扩展,当我从列表中删除一个项目时,模式显示会发生变化,但是,当我修改它时不会改变 orderItems 的长度,而只是其中一个对象的值。 正如我之前提到的,我找到了一些答案,他们说我应该像这样设置依赖项:...Object.values(&lt;contextVariable&gt;) 在技术上有效,但导致反应抱怨 *传递给 useEffect 的最终参数改变了渲染之间的大小。此数组的顺序和大小必须保持不变。 * 当我关闭并重新打开模式时,显示的值会更改为正确的值,更改 props.isOpen 表示问题出在上下文依赖中

【问题讨论】:

通过提供这段代码,我相信您会获得更多帮助。 如果对项目的引用没有改变,useEffect 将不会再次运行。这完全取决于你如何更新项目(你不应该改变状态)。 能否告诉我们 useEffect 包含什么以及如何更新状态(context.items)? @jperl 这是我认为问题所在代码的核心部分,希望我没有错过任何重要的内容 我不知道您在哪里找到这些告诉您使用...Object.values(&lt;contextVariable&gt;) 的答案,但请不要这样做,这太可怕了。 @EmmaJoe 实际上为您提供了一个很好的例子。你注意到区别了吗?正如我告诉你的,只要引用没有改变,useEffect 就不会重新运行。 EmmaJoe 解构了先前的值以构建一个新值,例如 let updatedCart = [...cart]。这样我们就得到了一个新的参考。你没有,你取了以前的值并对其应用了 unshift 。这很糟糕,您不仅改变了状态,而且还没有获得新的引用。 【参考方案1】:

您可以先创建您的应用上下文,如下所示,我将使用购物车的示例

import * as React from "react"

const AppContext = React.createContext(
  cart:[]
);

const AppContextProvider = (props) => 
  const [cart,setCart] = React.useState([])

  const addCartItem = (newItem)=>
    let updatedCart = [...cart];
    updatedCart.push(newItem)
    setCart(updatedCart)
    
  
  
  return <AppContext.Provider value=
    cart
  >props.children</AppContext.Provider>;
;

const useAppContext = () => React.useContext(AppContext);

export  AppContextProvider, useAppContext ;

然后,您可以在应用程序的任何位置使用应用程序上下文,如下所示,只要购物车的长度发生变化,您就会在购物车中收到通知

import * as React from "react";
import  useAppContext  from "../../context/app,context";

const ShoppingCart: React.FC = () => 
  const appContext = useAppContext();

  React.useEffect(() => 
    console.log(appContext.cart.length);
  , [appContext.cart]);
  return <div>appContext.cart.length</div>;
;

export default ShoppingCart;

【讨论】:

谢谢你的回答,虽然一开始我不明白为什么,这是正确的解决方案。 对于像我这样在查看此代码后仍然不明白的人,核心部分在问题下的 cmets 中进行了解释。【参考方案2】:

您可以尝试将上下文变量传递给 useEffect 依赖数组,并在 useEffect 主体内部执行检查以查看值是否不为空。

【讨论】:

以上是关于如何正确使用 useEffect 与 useContext 作为依赖的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 useEffect 正确地在数组中获取和保存数据

如何在数组依赖中正确使用 useEffect 挂钩。我从 redux 商店传递了状态,但我的组件仍然无限渲染

如何使用 React Hooks 和 Context API 正确地将来自 useEffect 内部调用的多个端点的数据添加到状态对象?

如何正确地将事件侦听器添加到 React useEffect 挂钩?

如何将 UseQuery 与 useEffect 一起使用?

如何使用 jest/enzyme 测试 useEffect 与 useDispatch 挂钩?