React:在useEffect中调用上下文函数时防止无限循环

React: Prevent infinite Loop when calling context-functions in useEffect

在我的 React 应用程序中,我正在渲染 <Item> 组件的不同实例,我希望它们 register/unregister 在上下文中,具体取决于它们当前是否已安装。

我正在使用两个上下文(ItemContext 提供注册项,ItemContextSetters 提供 register/unregister 的功能)。

const ItemContext = React.createContext({});
const ItemContextSetters = React.createContext({
  registerItem: (id, data) => undefined,
  unregisterItem: (id) => undefined
});

function ContextController(props) {
  const [items, setItems] = useState({});

  const unregisterItem = useCallback(
    (id) => {
      const itemsUpdate = { ...items };
      delete itemsUpdate[id];
      setItems(itemsUpdate);
    },
    [items]
  );

  const registerItem = useCallback(
    (id, data) => {
      if (items.hasOwnProperty(id) && items[id] === data) {
        return;
      }

      const itemsUpdate = { ...items, [id]: data };
      setItems(itemsUpdate);
    },
    [items]
  );

  return (
    <ItemContext.Provider value={{ items }}>
      <ItemContextSetters.Provider value={{ registerItem, unregisterItem }}>
        {props.children}
      </ItemContextSetters.Provider>
    </ItemContext.Provider>
  );
} 

<Item> 组件应在安装时自行注册,或在卸载时取消注册 props.data 更改。所以我认为可以用 useEffect:

非常干净地完成
function Item(props) {
  const itemContextSetters = useContext(ItemContextSetters);

  useEffect(() => {
    itemContextSetters.registerItem(props.id, props.data);

    return () => {
      itemContextSetters.unregisterItem(props.id);
    };
  }, [itemContextSetters, props.id, props.data]);

  ...
}

完整示例见此codesandbox

现在,问题是 这给了我一个无限循环,我不知道如何做得更好。循环是这样发生的(我相信):

我真的想不出一个干净的解决方案来避免这个问题。我是否滥用了任何钩子或上下文 api?你能帮我写出这样一个 register/unregister 上下文的一般模式吗,这个 register/unregister 上下文被组件调用在它们的 useEffect-body 和 useEffect-cleanup 中?

我不想做的事情:

谢谢!

您可以使您的 setter 具有稳定的引用,因为它们实际上并不需要依赖于 items:

const ItemContext = React.createContext({});
const ItemContextSetters = React.createContext({
  registerItem: (id, data) => undefined,
  unregisterItem: (id) => undefined
});

function ContextController(props) {
  const [items, setItems] = useState({});

  const unregisterItem = useCallback(
    (id) => {
      setItems(currentItems => {
        const itemsUpdate = { ...currentItems };
        delete itemsUpdate[id];
        return itemsUpdate
      });
    },
    []
  );

  const registerItem = useCallback(
    (id, data) => {

      setItems(currentItems => {
        if (currentItems.hasOwnProperty(id) && currentItems[id] === data) {
          return currentItems;
        }
        return { ...currentItems, [id]: data }
      } );
    },
    []
  );
  
  const itemsSetters = useMemo(() => ({ registerItem, unregisterItem }), [registerItem, unregisterItem])

  return (
    <ItemContext.Provider value={{ items }}>
      <ItemContextSetters.Provider value={itemsSetters}>
        {props.children}
      </ItemContextSetters.Provider>
    </ItemContext.Provider>
  );
}

现在你的效果应该可以正常工作了