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