javascript - 使用 React Context 作为简单 toast 通知系统的 useEffect 依赖数组的一部分

标签 javascript reactjs react-hooks

我正在使用 React Context 构建一个简单的 toast 通知系统。这是一个简化但完全有效的示例的链接,该示例显示了问题 https://codesandbox.io/s/currying-dust-kw00n .

我的页面组件封装在 HOC 中,使我能够在此页面内以编程方式添加、删除和删除所有 toast。该演示有一个用于添加 toast 通知的按钮和一个用于更改 activeStep 的按钮(想象这是一个多步骤表单)。当 activeStep 更改时,我希望删除所有 toast。

最初我使用以下方法做到了这一点...

useEffect(() => {
  toastManager.removeAll();
}, [activeStep]);

...这按我的预期工作,但是有一个react-hooks/exhaustive-deps ESLint 警告,因为toastManager 不在依赖项数组中。将 toastManager 添加到数组会导致 toast 在添加后立即被删除。

我想我可以使用useCallback解决这个问题...

const stableToastManager = useCallback(toastManager, []);
useEffect(() => {
  stableToastManager.removeAll();
}, [activeStep, stableToastManager]);

...但是,这不仅不起作用,而且我宁愿从源头解决问题,所以我不需要每次我想要这种功能时都这样做,因为它很可能被用在很多地方。

这就是我被困住的地方。我不确定如何更改我的上下文,这样我就不需要在 HOC 包装的组件中添加额外的逻辑。

export const ToastProvider = ({ children }) => {
  const [toasts, setToasts] = useState([]);

  const add = (content, options) => {
    // We use the content as the id as it prevents the same toast
    // being added multiple times
    const toast = { content, id: content, ...options };
    setToasts([...toasts, toast]);
  };

  const remove = id => {
    const newToasts = toasts.filter(t => t.id !== id);
    setToasts(newToasts);
  };

  const removeAll = () => {
    if (toasts.length > 0) {
      setToasts([]);
    }
  };

  return (
    <ToastContext.Provider value={{ add, remove, removeAll }}>
      {children}

      <div
        style={{
          position: `fixed`,
          top: `10px`,
          right: `10px`,
          display: `flex`,
          flexDirection: `column`
        }}
      >
        {toasts.map(({ content, id, ...rest }) => {
          return (
            <button onClick={() => remove(id)} {...rest}>
              {content}
            </button>
          );
        })}
      </div>
    </ToastContext.Provider>
  );
};

export const withToastManager = Component => props => {
  return (
    <ToastContext.Consumer>
      {context => {
        return <Component toastManager={context} {...props} />;
      }}
    </ToastContext.Consumer>
  );
};

最佳答案

如果你想“从核心修复它”,你需要修复ToastProvider:

const add = useCallback((content, options) => {
  const toast = { content, id: content, ...options };
  setToasts(pToasts => [...pToasts, toast]);
}, []);

const remove = useCallback(id => {
  setToasts(p => p.filter(t => t.id !== id));
}, []);

const removeAll = useCallback(() => {
  setToasts(p => (p.length > 0 ? [] : p));
}, []);


const store = useMemo(() => ({ add, remove, removeAll }), [
  add,
  remove,
  removeAll
]);

然后,useEffect 将按预期工作,因为问题是当它需要成为单例时,您在每个渲染上重新初始化 ToastProvider 功能。

useEffect(() => {
  toastManager.removeAll();
}, [activeStep, toastManager]);

此外,我建议添加自定义 Hook 功能作为默认用例,并仅为类组件提供包装器。

换句话说,不要在功能组件上使用包装器(withToastManager),将其用于类,因为它被认为是反模式,您可以使用 useContext它,所以你的库应该公开它。

// @ toastContext.js
export const useToast = () => {
  const context = useContext(ToastContext);
  return context;
};

// @ page.js
import React, { useState, useEffect } from 'react';
import { useToast } from './toastContext';

const Page = () => {
  const [activeStep, setActiveStep] = useState(1);
  const { removeAll, add } = useToast();

  useEffect(() => {
    removeAll();
  }, [activeStep, removeAll]);

  return (
    <div>
      <h1>Page {activeStep}</h1>

      <button
        onClick={() => {
          add(`Toast at ${Date.now()}!`);
        }}
      >
        Add Toast
      </button>

      <button
        onClick={() => {
          setActiveStep(activeStep + 1);
        }}
      >
        Change Step
      </button>
    </div>
  );
};

export default Page;

Edit intelligent-lake-2j51q

关于javascript - 使用 React Context 作为简单 toast 通知系统的 useEffect 依赖数组的一部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59425855/

相关文章:

html - 从 React Hooks GET 映射出 DOM 节点

javascript - CSS - 是否可以使用纯 CSS 卡住 html 表格顶部的标题以防止滚动?

javascript - PHP 简单 HTML DOM : How Do I Find Urls Exist In Javascript

javascript - 尝试使用 React 在模态组件之间切换

reactjs - 如何通过流程输入高阶组件

reactjs - 修改react-query的查询函数中的状态

javascript - 使用 JavaScript 获取 innerHTML 文本时出现问题

javascript - 正确计算随机数生成程序(javascript)的复杂度?

reactjs - 如何对 Electron 应用程序进行性能测试?

reactjs - 如何使用 react Hook 删除查询参数?